[WEB-436] chore: added h4 to h6 heading options (#4304)

* chore: added h4 to h6 heading options

* fix: build errors
This commit is contained in:
Aaryan Khandelwal 2024-04-29 16:04:37 +05:30 committed by GitHub
parent 49a6c9582c
commit 84fd1dca4b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 227 additions and 69 deletions

View File

@ -142,11 +142,11 @@ export const useEditor = ({
executeMenuItemCommand: (itemName: EditorMenuItemNames) => { executeMenuItemCommand: (itemName: EditorMenuItemNames) => {
const editorItems = getEditorMenuItems(editorRef.current, uploadFile); const editorItems = getEditorMenuItems(editorRef.current, uploadFile);
const getEditorMenuItem = (itemName: EditorMenuItemNames) => editorItems.find((item) => item.name === itemName); const getEditorMenuItem = (itemName: EditorMenuItemNames) => editorItems.find((item) => item.key === itemName);
const item = getEditorMenuItem(itemName); const item = getEditorMenuItem(itemName);
if (item) { if (item) {
if (item.name === "image") { if (item.key === "image") {
item.command(savedSelection); item.command(savedSelection);
} else { } else {
item.command(); item.command();
@ -158,7 +158,7 @@ export const useEditor = ({
isMenuItemActive: (itemName: EditorMenuItemNames): boolean => { isMenuItemActive: (itemName: EditorMenuItemNames): boolean => {
const editorItems = getEditorMenuItems(editorRef.current, uploadFile); const editorItems = getEditorMenuItems(editorRef.current, uploadFile);
const getEditorMenuItem = (itemName: EditorMenuItemNames) => editorItems.find((item) => item.name === itemName); const getEditorMenuItem = (itemName: EditorMenuItemNames) => editorItems.find((item) => item.key === itemName);
const item = getEditorMenuItem(itemName); const item = getEditorMenuItem(itemName);
return item ? item.isActive() : false; return item ? item.isActive() : false;
}, },

View File

@ -4,6 +4,11 @@ import { findTableAncestor } from "src/lib/utils";
import { Selection } from "@tiptap/pm/state"; import { Selection } from "@tiptap/pm/state";
import { UploadImage } from "src/types/upload-image"; import { UploadImage } from "src/types/upload-image";
export const setText = (editor: Editor, range?: Range) => {
if (range) editor.chain().focus().deleteRange(range).clearNodes().run();
else editor.chain().focus().clearNodes().run();
};
export const toggleHeadingOne = (editor: Editor, range?: Range) => { export const toggleHeadingOne = (editor: Editor, range?: Range) => {
if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 1 }).run(); if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 1 }).run();
else editor.chain().focus().toggleHeading({ level: 1 }).run(); else editor.chain().focus().toggleHeading({ level: 1 }).run();
@ -19,6 +24,21 @@ export const toggleHeadingThree = (editor: Editor, range?: Range) => {
else editor.chain().focus().toggleHeading({ level: 3 }).run(); else editor.chain().focus().toggleHeading({ level: 3 }).run();
}; };
export const toggleHeadingFour = (editor: Editor, range?: Range) => {
if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 4 }).run();
else editor.chain().focus().toggleHeading({ level: 4 }).run();
};
export const toggleHeadingFive = (editor: Editor, range?: Range) => {
if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 5 }).run();
else editor.chain().focus().toggleHeading({ level: 5 }).run();
};
export const toggleHeadingSix = (editor: Editor, range?: Range) => {
if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 6 }).run();
else editor.chain().focus().toggleHeading({ level: 6 }).run();
};
export const toggleBold = (editor: Editor, range?: Range) => { export const toggleBold = (editor: Editor, range?: Range) => {
if (range) editor.chain().focus().deleteRange(range).toggleBold().run(); if (range) editor.chain().focus().deleteRange(range).toggleBold().run();
else editor.chain().focus().toggleBold().run(); else editor.chain().focus().toggleBold().run();

View File

@ -330,6 +330,29 @@ ul[data-type="taskList"] ul[data-type="taskList"] {
line-height: 1.3; line-height: 1.3;
} }
.prose :where(h4):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
margin-top: 1rem;
margin-bottom: 1px;
font-size: 1rem;
line-height: 1.5;
}
.prose :where(h5):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
margin-top: 1rem;
margin-bottom: 1px;
font-size: 0.9rem;
font-weight: 600;
line-height: 1.5;
}
.prose :where(h6):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
margin-top: 1rem;
margin-bottom: 1px;
font-size: 0.83rem;
font-weight: 600;
line-height: 1.5;
}
.prose :where(p):not(:where([class~="not-prose"], [class~="not-prose"] *)) { .prose :where(p):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
margin-top: 0.25rem; margin-top: 0.25rem;
margin-bottom: 1px; margin-bottom: 1px;

View File

@ -13,16 +13,24 @@ import {
UnderlineIcon, UnderlineIcon,
StrikethroughIcon, StrikethroughIcon,
CodeIcon, CodeIcon,
Heading4,
Heading5,
Heading6,
CaseSensitive,
} from "lucide-react"; } from "lucide-react";
import { Editor } from "@tiptap/react"; import { Editor } from "@tiptap/react";
import { import {
insertImageCommand, insertImageCommand,
insertTableCommand, insertTableCommand,
setText,
toggleBlockquote, toggleBlockquote,
toggleBold, toggleBold,
toggleBulletList, toggleBulletList,
toggleCodeBlock, toggleCodeBlock,
toggleHeadingFive,
toggleHeadingFour,
toggleHeadingOne, toggleHeadingOne,
toggleHeadingSix,
toggleHeadingThree, toggleHeadingThree,
toggleHeadingTwo, toggleHeadingTwo,
toggleItalic, toggleItalic,
@ -36,15 +44,26 @@ import { UploadImage } from "src/types/upload-image";
import { Selection } from "@tiptap/pm/state"; import { Selection } from "@tiptap/pm/state";
export interface EditorMenuItem { export interface EditorMenuItem {
key: string;
name: string; name: string;
isActive: () => boolean; isActive: () => boolean;
command: () => void; command: () => void;
icon: LucideIconType; icon: LucideIconType;
} }
export const TextItem = (editor: Editor) =>
({
key: "text",
name: "Text",
isActive: () => editor.isActive("paragraph"),
command: () => setText(editor),
icon: CaseSensitive,
}) as const satisfies EditorMenuItem;
export const HeadingOneItem = (editor: Editor) => export const HeadingOneItem = (editor: Editor) =>
({ ({
name: "H1", key: "h1",
name: "Heading 1",
isActive: () => editor.isActive("heading", { level: 1 }), isActive: () => editor.isActive("heading", { level: 1 }),
command: () => toggleHeadingOne(editor), command: () => toggleHeadingOne(editor),
icon: Heading1, icon: Heading1,
@ -52,7 +71,8 @@ export const HeadingOneItem = (editor: Editor) =>
export const HeadingTwoItem = (editor: Editor) => export const HeadingTwoItem = (editor: Editor) =>
({ ({
name: "H2", key: "h2",
name: "Heading 2",
isActive: () => editor.isActive("heading", { level: 2 }), isActive: () => editor.isActive("heading", { level: 2 }),
command: () => toggleHeadingTwo(editor), command: () => toggleHeadingTwo(editor),
icon: Heading2, icon: Heading2,
@ -60,15 +80,44 @@ export const HeadingTwoItem = (editor: Editor) =>
export const HeadingThreeItem = (editor: Editor) => export const HeadingThreeItem = (editor: Editor) =>
({ ({
name: "H3", key: "h3",
name: "Heading 3",
isActive: () => editor.isActive("heading", { level: 3 }), isActive: () => editor.isActive("heading", { level: 3 }),
command: () => toggleHeadingThree(editor), command: () => toggleHeadingThree(editor),
icon: Heading3, icon: Heading3,
}) as const satisfies EditorMenuItem; }) as const satisfies EditorMenuItem;
export const HeadingFourItem = (editor: Editor) =>
({
key: "h4",
name: "Heading 4",
isActive: () => editor.isActive("heading", { level: 4 }),
command: () => toggleHeadingFour(editor),
icon: Heading4,
}) as const satisfies EditorMenuItem;
export const HeadingFiveItem = (editor: Editor) =>
({
key: "h5",
name: "Heading 5",
isActive: () => editor.isActive("heading", { level: 5 }),
command: () => toggleHeadingFive(editor),
icon: Heading5,
}) as const satisfies EditorMenuItem;
export const HeadingSixItem = (editor: Editor) =>
({
key: "h6",
name: "Heading 6",
isActive: () => editor.isActive("heading", { level: 6 }),
command: () => toggleHeadingSix(editor),
icon: Heading6,
}) as const satisfies EditorMenuItem;
export const BoldItem = (editor: Editor) => export const BoldItem = (editor: Editor) =>
({ ({
name: "bold", key: "bold",
name: "Bold",
isActive: () => editor?.isActive("bold"), isActive: () => editor?.isActive("bold"),
command: () => toggleBold(editor), command: () => toggleBold(editor),
icon: BoldIcon, icon: BoldIcon,
@ -76,7 +125,8 @@ export const BoldItem = (editor: Editor) =>
export const ItalicItem = (editor: Editor) => export const ItalicItem = (editor: Editor) =>
({ ({
name: "italic", key: "italic",
name: "Italic",
isActive: () => editor?.isActive("italic"), isActive: () => editor?.isActive("italic"),
command: () => toggleItalic(editor), command: () => toggleItalic(editor),
icon: ItalicIcon, icon: ItalicIcon,
@ -84,7 +134,8 @@ export const ItalicItem = (editor: Editor) =>
export const UnderLineItem = (editor: Editor) => export const UnderLineItem = (editor: Editor) =>
({ ({
name: "underline", key: "underline",
name: "Underline",
isActive: () => editor?.isActive("underline"), isActive: () => editor?.isActive("underline"),
command: () => toggleUnderline(editor), command: () => toggleUnderline(editor),
icon: UnderlineIcon, icon: UnderlineIcon,
@ -92,7 +143,8 @@ export const UnderLineItem = (editor: Editor) =>
export const StrikeThroughItem = (editor: Editor) => export const StrikeThroughItem = (editor: Editor) =>
({ ({
name: "strike", key: "strikethrough",
name: "Strikethrough",
isActive: () => editor?.isActive("strike"), isActive: () => editor?.isActive("strike"),
command: () => toggleStrike(editor), command: () => toggleStrike(editor),
icon: StrikethroughIcon, icon: StrikethroughIcon,
@ -100,47 +152,53 @@ export const StrikeThroughItem = (editor: Editor) =>
export const BulletListItem = (editor: Editor) => export const BulletListItem = (editor: Editor) =>
({ ({
name: "bullet-list", key: "bulleted-list",
name: "Bulleted list",
isActive: () => editor?.isActive("bulletList"), isActive: () => editor?.isActive("bulletList"),
command: () => toggleBulletList(editor), command: () => toggleBulletList(editor),
icon: ListIcon, icon: ListIcon,
}) as const satisfies EditorMenuItem; }) as const satisfies EditorMenuItem;
export const TodoListItem = (editor: Editor) =>
({
name: "To-do List",
isActive: () => editor.isActive("taskItem"),
command: () => toggleTaskList(editor),
icon: CheckSquare,
}) as const satisfies EditorMenuItem;
export const CodeItem = (editor: Editor) =>
({
name: "code",
isActive: () => editor?.isActive("code") || editor?.isActive("codeBlock"),
command: () => toggleCodeBlock(editor),
icon: CodeIcon,
}) as const satisfies EditorMenuItem;
export const NumberedListItem = (editor: Editor) => export const NumberedListItem = (editor: Editor) =>
({ ({
name: "ordered-list", key: "numbered-list",
name: "Numbered list",
isActive: () => editor?.isActive("orderedList"), isActive: () => editor?.isActive("orderedList"),
command: () => toggleOrderedList(editor), command: () => toggleOrderedList(editor),
icon: ListOrderedIcon, icon: ListOrderedIcon,
}) as const satisfies EditorMenuItem; }) as const satisfies EditorMenuItem;
export const TodoListItem = (editor: Editor) =>
({
key: "to-do-list",
name: "To-do list",
isActive: () => editor.isActive("taskItem"),
command: () => toggleTaskList(editor),
icon: CheckSquare,
}) as const satisfies EditorMenuItem;
export const QuoteItem = (editor: Editor) => export const QuoteItem = (editor: Editor) =>
({ ({
name: "quote", key: "quote",
name: "Quote",
isActive: () => editor?.isActive("blockquote"), isActive: () => editor?.isActive("blockquote"),
command: () => toggleBlockquote(editor), command: () => toggleBlockquote(editor),
icon: QuoteIcon, icon: QuoteIcon,
}) as const satisfies EditorMenuItem; }) as const satisfies EditorMenuItem;
export const CodeItem = (editor: Editor) =>
({
key: "code",
name: "Code",
isActive: () => editor?.isActive("code") || editor?.isActive("codeBlock"),
command: () => toggleCodeBlock(editor),
icon: CodeIcon,
}) as const satisfies EditorMenuItem;
export const TableItem = (editor: Editor) => export const TableItem = (editor: Editor) =>
({ ({
name: "table", key: "table",
name: "Table",
isActive: () => editor?.isActive("table"), isActive: () => editor?.isActive("table"),
command: () => insertTableCommand(editor), command: () => insertTableCommand(editor),
icon: TableIcon, icon: TableIcon,
@ -148,7 +206,8 @@ export const TableItem = (editor: Editor) =>
export const ImageItem = (editor: Editor, uploadFile: UploadImage) => export const ImageItem = (editor: Editor, uploadFile: UploadImage) =>
({ ({
name: "image", key: "image",
name: "Image",
isActive: () => editor?.isActive("image"), isActive: () => editor?.isActive("image"),
command: (savedSelection: Selection | null) => insertImageCommand(editor, uploadFile, savedSelection), command: (savedSelection: Selection | null) => insertImageCommand(editor, uploadFile, savedSelection),
icon: ImageIcon, icon: ImageIcon,
@ -159,9 +218,13 @@ export function getEditorMenuItems(editor: Editor | null, uploadFile: UploadImag
return []; return [];
} }
return [ return [
TextItem(editor),
HeadingOneItem(editor), HeadingOneItem(editor),
HeadingTwoItem(editor), HeadingTwoItem(editor),
HeadingThreeItem(editor), HeadingThreeItem(editor),
HeadingFourItem(editor),
HeadingFiveItem(editor),
HeadingSixItem(editor),
BoldItem(editor), BoldItem(editor),
ItalicItem(editor), ItalicItem(editor),
UnderLineItem(editor), UnderLineItem(editor),
@ -177,7 +240,7 @@ export function getEditorMenuItems(editor: Editor | null, uploadFile: UploadImag
} }
export type EditorMenuItemNames = ReturnType<typeof getEditorMenuItems> extends (infer U)[] export type EditorMenuItemNames = ReturnType<typeof getEditorMenuItems> extends (infer U)[]
? U extends { name: infer N } ? U extends { key: infer N }
? N ? N
: never : never
: never; : never;

View File

@ -67,11 +67,13 @@ export default function BlockMenu(props: BlockMenuProps) {
popup.current?.hide(); popup.current?.hide();
}; };
document.addEventListener("click", handleClickDragHandle); document.addEventListener("click", handleClickDragHandle);
document.addEventListener("contextmenu", handleClickDragHandle);
document.addEventListener("keydown", handleKeyDown); document.addEventListener("keydown", handleKeyDown);
document.addEventListener("scroll", handleScroll, true); // Using capture phase document.addEventListener("scroll", handleScroll, true); // Using capture phase
return () => { return () => {
document.removeEventListener("click", handleClickDragHandle); document.removeEventListener("click", handleClickDragHandle);
document.removeEventListener("contextmenu", handleClickDragHandle);
document.removeEventListener("keydown", handleKeyDown); document.removeEventListener("keydown", handleKeyDown);
document.removeEventListener("scroll", handleScroll, true); document.removeEventListener("scroll", handleScroll, true);
}; };

View File

@ -225,6 +225,9 @@ function DragHandle(options: DragHandleOptions) {
dragHandleElement.addEventListener("click", (e) => { dragHandleElement.addEventListener("click", (e) => {
handleClick(e, view); handleClick(e, view);
}); });
dragHandleElement.addEventListener("contextmenu", (e) => {
handleClick(e, view);
});
dragHandleElement.addEventListener("drag", (e) => { dragHandleElement.addEventListener("drag", (e) => {
hideDragHandle(); hideDragHandle();

View File

@ -15,6 +15,7 @@ import {
} from "@plane/editor-core"; } from "@plane/editor-core";
export interface BubbleMenuItem { export interface BubbleMenuItem {
key: string;
name: string; name: string;
isActive: () => boolean; isActive: () => boolean;
command: () => void; command: () => void;

View File

@ -8,9 +8,13 @@ import {
QuoteItem, QuoteItem,
CodeItem, CodeItem,
TodoListItem, TodoListItem,
TextItem,
HeadingFourItem,
HeadingFiveItem,
HeadingSixItem,
} from "@plane/editor-core"; } from "@plane/editor-core";
import { Editor } from "@tiptap/react"; import { Editor } from "@tiptap/react";
import { Check, ChevronDown, TextIcon } from "lucide-react"; import { Check, ChevronDown } from "lucide-react";
import { Dispatch, FC, SetStateAction } from "react"; import { Dispatch, FC, SetStateAction } from "react";
import { BubbleMenuItem } from "."; import { BubbleMenuItem } from ".";
@ -23,18 +27,16 @@ interface NodeSelectorProps {
export const NodeSelector: FC<NodeSelectorProps> = ({ editor, isOpen, setIsOpen }) => { export const NodeSelector: FC<NodeSelectorProps> = ({ editor, isOpen, setIsOpen }) => {
const items: BubbleMenuItem[] = [ const items: BubbleMenuItem[] = [
{ TextItem(editor),
name: "Text",
icon: TextIcon,
command: () => editor.chain().focus().clearNodes().run(),
isActive: () => editor.isActive("paragraph") && !editor.isActive("bulletList") && !editor.isActive("orderedList"),
},
HeadingOneItem(editor), HeadingOneItem(editor),
HeadingTwoItem(editor), HeadingTwoItem(editor),
HeadingThreeItem(editor), HeadingThreeItem(editor),
TodoListItem(editor), HeadingFourItem(editor),
HeadingFiveItem(editor),
HeadingSixItem(editor),
BulletListItem(editor), BulletListItem(editor),
NumberedListItem(editor), NumberedListItem(editor),
TodoListItem(editor),
QuoteItem(editor), QuoteItem(editor),
CodeItem(editor), CodeItem(editor),
]; ];
@ -58,7 +60,7 @@ export const NodeSelector: FC<NodeSelectorProps> = ({ editor, isOpen, setIsOpen
</button> </button>
{isOpen && ( {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"> <section className="fixed top-full z-[99999] mt-1 flex w-48 flex-col overflow-hidden rounded-md border-[0.5px] border-custom-border-300 bg-custom-background-100 px-2 py-2.5 shadow-custom-shadow-rg animate-in fade-in slide-in-from-top-1">
{items.map((item) => ( {items.map((item) => (
<button <button
key={item.name} key={item.name}
@ -69,19 +71,17 @@ export const NodeSelector: FC<NodeSelectorProps> = ({ editor, isOpen, setIsOpen
e.stopPropagation(); e.stopPropagation();
}} }}
className={cn( 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", "flex items-center justify-between rounded px-1 py-1.5 text-sm text-custom-text-200 hover:bg-custom-background-80",
{ {
"bg-custom-primary-100/5 text-custom-text-100": activeItem.name === item.name, "bg-custom-background-80": activeItem.name === item.name,
} }
)} )}
> >
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<div className="rounded-sm border border-custom-border-300 p-1"> <item.icon className="size-3 flex-shrink-0" />
<item.icon className="h-3 w-3" />
</div>
<span>{item.name}</span> <span>{item.name}</span>
</div> </div>
{activeItem.name === item.name && <Check className="h-4 w-4" />} {activeItem.name === item.name && <Check className="size-3 text-custom-text-300 flex-shrink-0" />}
</button> </button>
))} ))}
</section> </section>

View File

@ -4,6 +4,9 @@ import {
Heading1, Heading1,
Heading2, Heading2,
Heading3, Heading3,
Heading4,
Heading5,
Heading6,
Image, Image,
Italic, Italic,
List, List,
@ -29,14 +32,17 @@ export type ToolbarMenuItem = {
}; };
export const BASIC_MARK_ITEMS: ToolbarMenuItem[] = [ export const BASIC_MARK_ITEMS: ToolbarMenuItem[] = [
{ key: "H1", name: "Heading 1", icon: Heading1, editors: ["document"] }, { key: "h1", name: "Heading 1", icon: Heading1, editors: ["document"] },
{ key: "H2", name: "Heading 2", icon: Heading2, editors: ["document"] }, { key: "h2", name: "Heading 2", icon: Heading2, editors: ["document"] },
{ key: "H3", name: "Heading 3", icon: Heading3, editors: ["document"] }, { key: "h3", name: "Heading 3", icon: Heading3, editors: ["document"] },
{ key: "h4", name: "Heading 4", icon: Heading4, editors: ["document"] },
{ key: "h5", name: "Heading 5", icon: Heading5, editors: ["document"] },
{ key: "h6", name: "Heading 6", icon: Heading6, editors: ["document"] },
{ key: "bold", name: "Bold", icon: Bold, shortcut: ["Cmd", "B"], editors: ["lite", "document"] }, { key: "bold", name: "Bold", icon: Bold, shortcut: ["Cmd", "B"], editors: ["lite", "document"] },
{ key: "italic", name: "Italic", icon: Italic, shortcut: ["Cmd", "I"], editors: ["lite", "document"] }, { key: "italic", name: "Italic", icon: Italic, shortcut: ["Cmd", "I"], editors: ["lite", "document"] },
{ key: "underline", name: "Underline", icon: Underline, shortcut: ["Cmd", "U"], editors: ["lite", "document"] }, { key: "underline", name: "Underline", icon: Underline, shortcut: ["Cmd", "U"], editors: ["lite", "document"] },
{ {
key: "strike", key: "strikethrough",
name: "Strikethrough", name: "Strikethrough",
icon: Strikethrough, icon: Strikethrough,
shortcut: ["Cmd", "Shift", "S"], shortcut: ["Cmd", "Shift", "S"],
@ -46,21 +52,21 @@ export const BASIC_MARK_ITEMS: ToolbarMenuItem[] = [
export const LIST_ITEMS: ToolbarMenuItem[] = [ export const LIST_ITEMS: ToolbarMenuItem[] = [
{ {
key: "bullet-list", key: "bulleted-list",
name: "Bulleted list", name: "Bulleted list",
icon: List, icon: List,
shortcut: ["Cmd", "Shift", "7"], shortcut: ["Cmd", "Shift", "7"],
editors: ["lite", "document"], editors: ["lite", "document"],
}, },
{ {
key: "ordered-list", key: "numbered-list",
name: "Numbered list", name: "Numbered list",
icon: ListOrdered, icon: ListOrdered,
shortcut: ["Cmd", "Shift", "8"], shortcut: ["Cmd", "Shift", "8"],
editors: ["lite", "document"], editors: ["lite", "document"],
}, },
{ {
key: "To-do List", key: "to-do-list",
name: "To-do list", name: "To-do list",
icon: ListTodo, icon: ListTodo,
shortcut: ["Cmd", "Shift", "9"], shortcut: ["Cmd", "Shift", "9"],

View File

@ -1,10 +1,11 @@
import React, { useEffect, useState, useCallback } from "react"; import React, { useEffect, useState, useCallback } from "react";
import { Check, ChevronDown } from "lucide-react";
// editor // editor
import { EditorMenuItemNames, EditorRefApi } from "@plane/document-editor"; import { EditorMenuItemNames, EditorRefApi } from "@plane/document-editor";
// ui // ui
import { Tooltip } from "@plane/ui"; import { CustomMenu, Tooltip } from "@plane/ui";
// constants // constants
import { TOOLBAR_ITEMS, ToolbarMenuItem } from "@/constants/editor"; import { TOOLBAR_ITEMS, TYPOGRAPHY_ITEMS, ToolbarMenuItem } from "@/constants/editor";
// helpers // helpers
import { cn } from "@/helpers/common.helper"; import { cn } from "@/helpers/common.helper";
@ -34,12 +35,12 @@ const ToolbarButton: React.FC<ToolbarButtonProps> = React.memo((props) => {
key={item.key} key={item.key}
type="button" type="button"
onClick={() => executeCommand(item.key)} onClick={() => executeCommand(item.key)}
className={cn("grid h-7 w-7 place-items-center rounded text-custom-text-300 hover:bg-custom-background-80", { className={cn("grid size-7 place-items-center rounded text-custom-text-300 hover:bg-custom-background-80", {
"bg-custom-background-80 text-custom-text-100": isActive, "bg-custom-background-80 text-custom-text-100": isActive,
})} })}
> >
<item.icon <item.icon
className={cn("h-4 w-4", { className={cn("size-4", {
"text-custom-text-100": isActive, "text-custom-text-100": isActive,
})} })}
/> />
@ -71,8 +72,36 @@ export const PageToolbar: React.FC<Props> = ({ editorRef }) => {
return () => unsubscribe(); return () => unsubscribe();
}, [editorRef, updateActiveStates]); }, [editorRef, updateActiveStates]);
const activeTypography = TYPOGRAPHY_ITEMS.find((item) => editorRef.isMenuItemActive(item.key));
return ( return (
<div className="flex flex-wrap items-center divide-x divide-custom-border-200"> <div className="flex flex-wrap items-center divide-x divide-custom-border-200">
<CustomMenu
customButton={
<span className="text-custom-text-300 text-sm border-[0.5px] border-custom-border-300 hover:bg-custom-background-80 h-7 w-24 rounded px-2 flex items-center justify-between gap-2 whitespace-nowrap text-left">
{activeTypography?.name || "Text"}
<ChevronDown className="flex-shrink-0 size-3" />
</span>
}
className="pr-2"
placement="bottom-start"
closeOnSelect
maxHeight="lg"
>
{TYPOGRAPHY_ITEMS.map((item) => (
<CustomMenu.MenuItem
key={item.key}
className="flex items-center justify-between gap-2"
onClick={() => editorRef.executeMenuItemCommand(item.key)}
>
<span className="flex items-center gap-2">
<item.icon className="size-3" />
{item.name}
</span>
{activeTypography?.key === item.key && <Check className="size-3 text-custom-text-300 flex-shrink-0" />}
</CustomMenu.MenuItem>
))}
</CustomMenu>
{Object.keys(toolbarItems).map((key) => ( {Object.keys(toolbarItems).map((key) => (
<div key={key} className="flex items-center gap-0.5 px-2 first:pl-0 last:pr-0"> <div key={key} className="flex items-center gap-0.5 px-2 first:pl-0 last:pr-0">
{toolbarItems[key].map((item) => ( {toolbarItems[key].map((item) => (

View File

@ -1,9 +1,13 @@
import { import {
Bold, Bold,
CaseSensitive,
Code2, Code2,
Heading1, Heading1,
Heading2, Heading2,
Heading3, Heading3,
Heading4,
Heading5,
Heading6,
Image, Image,
Italic, Italic,
List, List,
@ -28,15 +32,22 @@ export type ToolbarMenuItem = {
editors: TEditorTypes[]; editors: TEditorTypes[];
}; };
export const BASIC_MARK_ITEMS: ToolbarMenuItem[] = [ export const TYPOGRAPHY_ITEMS: ToolbarMenuItem[] = [
{ key: "H1", name: "Heading 1", icon: Heading1, editors: ["document"] }, { key: "text", name: "Text", icon: CaseSensitive, editors: ["document"] },
{ key: "H2", name: "Heading 2", icon: Heading2, editors: ["document"] }, { key: "h1", name: "Heading 1", icon: Heading1, editors: ["document"] },
{ key: "H3", name: "Heading 3", icon: Heading3, editors: ["document"] }, { key: "h2", name: "Heading 2", icon: Heading2, editors: ["document"] },
{ key: "h3", name: "Heading 3", icon: Heading3, editors: ["document"] },
{ key: "h4", name: "Heading 4", icon: Heading4, editors: ["document"] },
{ key: "h5", name: "Heading 5", icon: Heading5, editors: ["document"] },
{ key: "h6", name: "Heading 6", icon: Heading6, editors: ["document"] },
];
const BASIC_MARK_ITEMS: ToolbarMenuItem[] = [
{ key: "bold", name: "Bold", icon: Bold, shortcut: ["Cmd", "B"], editors: ["lite", "document"] }, { key: "bold", name: "Bold", icon: Bold, shortcut: ["Cmd", "B"], editors: ["lite", "document"] },
{ key: "italic", name: "Italic", icon: Italic, shortcut: ["Cmd", "I"], editors: ["lite", "document"] }, { key: "italic", name: "Italic", icon: Italic, shortcut: ["Cmd", "I"], editors: ["lite", "document"] },
{ key: "underline", name: "Underline", icon: Underline, shortcut: ["Cmd", "U"], editors: ["lite", "document"] }, { key: "underline", name: "Underline", icon: Underline, shortcut: ["Cmd", "U"], editors: ["lite", "document"] },
{ {
key: "strike", key: "strikethrough",
name: "Strikethrough", name: "Strikethrough",
icon: Strikethrough, icon: Strikethrough,
shortcut: ["Cmd", "Shift", "S"], shortcut: ["Cmd", "Shift", "S"],
@ -44,23 +55,23 @@ export const BASIC_MARK_ITEMS: ToolbarMenuItem[] = [
}, },
]; ];
export const LIST_ITEMS: ToolbarMenuItem[] = [ const LIST_ITEMS: ToolbarMenuItem[] = [
{ {
key: "bullet-list", key: "bulleted-list",
name: "Bulleted list", name: "Bulleted list",
icon: List, icon: List,
shortcut: ["Cmd", "Shift", "7"], shortcut: ["Cmd", "Shift", "7"],
editors: ["lite", "document"], editors: ["lite", "document"],
}, },
{ {
key: "ordered-list", key: "numbered-list",
name: "Numbered list", name: "Numbered list",
icon: ListOrdered, icon: ListOrdered,
shortcut: ["Cmd", "Shift", "8"], shortcut: ["Cmd", "Shift", "8"],
editors: ["lite", "document"], editors: ["lite", "document"],
}, },
{ {
key: "To-do List", key: "to-do-list",
name: "To-do list", name: "To-do list",
icon: ListTodo, icon: ListTodo,
shortcut: ["Cmd", "Shift", "9"], shortcut: ["Cmd", "Shift", "9"],
@ -68,12 +79,12 @@ export const LIST_ITEMS: ToolbarMenuItem[] = [
}, },
]; ];
export const USER_ACTION_ITEMS: ToolbarMenuItem[] = [ const USER_ACTION_ITEMS: ToolbarMenuItem[] = [
{ key: "quote", name: "Quote", icon: Quote, editors: ["lite", "document"] }, { key: "quote", name: "Quote", icon: Quote, editors: ["lite", "document"] },
{ key: "code", name: "Code", icon: Code2, editors: ["lite", "document"] }, { key: "code", name: "Code", icon: Code2, editors: ["lite", "document"] },
]; ];
export const COMPLEX_ITEMS: ToolbarMenuItem[] = [ const COMPLEX_ITEMS: ToolbarMenuItem[] = [
{ key: "table", name: "Table", icon: Table, editors: ["document"] }, { key: "table", name: "Table", icon: Table, editors: ["document"] },
{ key: "image", name: "Image", icon: Image, editors: ["lite", "document"] }, { key: "image", name: "Image", icon: Image, editors: ["lite", "document"] },
]; ];