[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:
parent
49a6c9582c
commit
84fd1dca4b
11 changed files with 227 additions and 69 deletions
|
|
@ -142,11 +142,11 @@ export const useEditor = ({
|
|||
executeMenuItemCommand: (itemName: EditorMenuItemNames) => {
|
||||
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);
|
||||
if (item) {
|
||||
if (item.name === "image") {
|
||||
if (item.key === "image") {
|
||||
item.command(savedSelection);
|
||||
} else {
|
||||
item.command();
|
||||
|
|
@ -158,7 +158,7 @@ export const useEditor = ({
|
|||
isMenuItemActive: (itemName: EditorMenuItemNames): boolean => {
|
||||
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);
|
||||
return item ? item.isActive() : false;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,6 +4,11 @@ import { findTableAncestor } from "src/lib/utils";
|
|||
import { Selection } from "@tiptap/pm/state";
|
||||
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) => {
|
||||
if (range) editor.chain().focus().deleteRange(range).setNode("heading", { 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();
|
||||
};
|
||||
|
||||
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) => {
|
||||
if (range) editor.chain().focus().deleteRange(range).toggleBold().run();
|
||||
else editor.chain().focus().toggleBold().run();
|
||||
|
|
|
|||
|
|
@ -330,6 +330,29 @@ ul[data-type="taskList"] ul[data-type="taskList"] {
|
|||
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"] *)) {
|
||||
margin-top: 0.25rem;
|
||||
margin-bottom: 1px;
|
||||
|
|
|
|||
|
|
@ -13,16 +13,24 @@ import {
|
|||
UnderlineIcon,
|
||||
StrikethroughIcon,
|
||||
CodeIcon,
|
||||
Heading4,
|
||||
Heading5,
|
||||
Heading6,
|
||||
CaseSensitive,
|
||||
} from "lucide-react";
|
||||
import { Editor } from "@tiptap/react";
|
||||
import {
|
||||
insertImageCommand,
|
||||
insertTableCommand,
|
||||
setText,
|
||||
toggleBlockquote,
|
||||
toggleBold,
|
||||
toggleBulletList,
|
||||
toggleCodeBlock,
|
||||
toggleHeadingFive,
|
||||
toggleHeadingFour,
|
||||
toggleHeadingOne,
|
||||
toggleHeadingSix,
|
||||
toggleHeadingThree,
|
||||
toggleHeadingTwo,
|
||||
toggleItalic,
|
||||
|
|
@ -36,15 +44,26 @@ import { UploadImage } from "src/types/upload-image";
|
|||
import { Selection } from "@tiptap/pm/state";
|
||||
|
||||
export interface EditorMenuItem {
|
||||
key: string;
|
||||
name: string;
|
||||
isActive: () => boolean;
|
||||
command: () => void;
|
||||
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) =>
|
||||
({
|
||||
name: "H1",
|
||||
key: "h1",
|
||||
name: "Heading 1",
|
||||
isActive: () => editor.isActive("heading", { level: 1 }),
|
||||
command: () => toggleHeadingOne(editor),
|
||||
icon: Heading1,
|
||||
|
|
@ -52,7 +71,8 @@ export const HeadingOneItem = (editor: Editor) =>
|
|||
|
||||
export const HeadingTwoItem = (editor: Editor) =>
|
||||
({
|
||||
name: "H2",
|
||||
key: "h2",
|
||||
name: "Heading 2",
|
||||
isActive: () => editor.isActive("heading", { level: 2 }),
|
||||
command: () => toggleHeadingTwo(editor),
|
||||
icon: Heading2,
|
||||
|
|
@ -60,15 +80,44 @@ export const HeadingTwoItem = (editor: Editor) =>
|
|||
|
||||
export const HeadingThreeItem = (editor: Editor) =>
|
||||
({
|
||||
name: "H3",
|
||||
key: "h3",
|
||||
name: "Heading 3",
|
||||
isActive: () => editor.isActive("heading", { level: 3 }),
|
||||
command: () => toggleHeadingThree(editor),
|
||||
icon: Heading3,
|
||||
}) 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) =>
|
||||
({
|
||||
name: "bold",
|
||||
key: "bold",
|
||||
name: "Bold",
|
||||
isActive: () => editor?.isActive("bold"),
|
||||
command: () => toggleBold(editor),
|
||||
icon: BoldIcon,
|
||||
|
|
@ -76,7 +125,8 @@ export const BoldItem = (editor: Editor) =>
|
|||
|
||||
export const ItalicItem = (editor: Editor) =>
|
||||
({
|
||||
name: "italic",
|
||||
key: "italic",
|
||||
name: "Italic",
|
||||
isActive: () => editor?.isActive("italic"),
|
||||
command: () => toggleItalic(editor),
|
||||
icon: ItalicIcon,
|
||||
|
|
@ -84,7 +134,8 @@ export const ItalicItem = (editor: Editor) =>
|
|||
|
||||
export const UnderLineItem = (editor: Editor) =>
|
||||
({
|
||||
name: "underline",
|
||||
key: "underline",
|
||||
name: "Underline",
|
||||
isActive: () => editor?.isActive("underline"),
|
||||
command: () => toggleUnderline(editor),
|
||||
icon: UnderlineIcon,
|
||||
|
|
@ -92,7 +143,8 @@ export const UnderLineItem = (editor: Editor) =>
|
|||
|
||||
export const StrikeThroughItem = (editor: Editor) =>
|
||||
({
|
||||
name: "strike",
|
||||
key: "strikethrough",
|
||||
name: "Strikethrough",
|
||||
isActive: () => editor?.isActive("strike"),
|
||||
command: () => toggleStrike(editor),
|
||||
icon: StrikethroughIcon,
|
||||
|
|
@ -100,47 +152,53 @@ export const StrikeThroughItem = (editor: Editor) =>
|
|||
|
||||
export const BulletListItem = (editor: Editor) =>
|
||||
({
|
||||
name: "bullet-list",
|
||||
key: "bulleted-list",
|
||||
name: "Bulleted list",
|
||||
isActive: () => editor?.isActive("bulletList"),
|
||||
command: () => toggleBulletList(editor),
|
||||
icon: ListIcon,
|
||||
}) 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) =>
|
||||
({
|
||||
name: "ordered-list",
|
||||
key: "numbered-list",
|
||||
name: "Numbered list",
|
||||
isActive: () => editor?.isActive("orderedList"),
|
||||
command: () => toggleOrderedList(editor),
|
||||
icon: ListOrderedIcon,
|
||||
}) 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) =>
|
||||
({
|
||||
name: "quote",
|
||||
key: "quote",
|
||||
name: "Quote",
|
||||
isActive: () => editor?.isActive("blockquote"),
|
||||
command: () => toggleBlockquote(editor),
|
||||
icon: QuoteIcon,
|
||||
}) 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) =>
|
||||
({
|
||||
name: "table",
|
||||
key: "table",
|
||||
name: "Table",
|
||||
isActive: () => editor?.isActive("table"),
|
||||
command: () => insertTableCommand(editor),
|
||||
icon: TableIcon,
|
||||
|
|
@ -148,7 +206,8 @@ export const TableItem = (editor: Editor) =>
|
|||
|
||||
export const ImageItem = (editor: Editor, uploadFile: UploadImage) =>
|
||||
({
|
||||
name: "image",
|
||||
key: "image",
|
||||
name: "Image",
|
||||
isActive: () => editor?.isActive("image"),
|
||||
command: (savedSelection: Selection | null) => insertImageCommand(editor, uploadFile, savedSelection),
|
||||
icon: ImageIcon,
|
||||
|
|
@ -159,9 +218,13 @@ export function getEditorMenuItems(editor: Editor | null, uploadFile: UploadImag
|
|||
return [];
|
||||
}
|
||||
return [
|
||||
TextItem(editor),
|
||||
HeadingOneItem(editor),
|
||||
HeadingTwoItem(editor),
|
||||
HeadingThreeItem(editor),
|
||||
HeadingFourItem(editor),
|
||||
HeadingFiveItem(editor),
|
||||
HeadingSixItem(editor),
|
||||
BoldItem(editor),
|
||||
ItalicItem(editor),
|
||||
UnderLineItem(editor),
|
||||
|
|
@ -177,7 +240,7 @@ export function getEditorMenuItems(editor: Editor | null, uploadFile: UploadImag
|
|||
}
|
||||
|
||||
export type EditorMenuItemNames = ReturnType<typeof getEditorMenuItems> extends (infer U)[]
|
||||
? U extends { name: infer N }
|
||||
? U extends { key: infer N }
|
||||
? N
|
||||
: never
|
||||
: never;
|
||||
|
|
|
|||
|
|
@ -67,11 +67,13 @@ export default function BlockMenu(props: BlockMenuProps) {
|
|||
popup.current?.hide();
|
||||
};
|
||||
document.addEventListener("click", handleClickDragHandle);
|
||||
document.addEventListener("contextmenu", handleClickDragHandle);
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
document.addEventListener("scroll", handleScroll, true); // Using capture phase
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("click", handleClickDragHandle);
|
||||
document.removeEventListener("contextmenu", handleClickDragHandle);
|
||||
document.removeEventListener("keydown", handleKeyDown);
|
||||
document.removeEventListener("scroll", handleScroll, true);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -225,6 +225,9 @@ function DragHandle(options: DragHandleOptions) {
|
|||
dragHandleElement.addEventListener("click", (e) => {
|
||||
handleClick(e, view);
|
||||
});
|
||||
dragHandleElement.addEventListener("contextmenu", (e) => {
|
||||
handleClick(e, view);
|
||||
});
|
||||
|
||||
dragHandleElement.addEventListener("drag", (e) => {
|
||||
hideDragHandle();
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
} from "@plane/editor-core";
|
||||
|
||||
export interface BubbleMenuItem {
|
||||
key: string;
|
||||
name: string;
|
||||
isActive: () => boolean;
|
||||
command: () => void;
|
||||
|
|
|
|||
|
|
@ -8,9 +8,13 @@ import {
|
|||
QuoteItem,
|
||||
CodeItem,
|
||||
TodoListItem,
|
||||
TextItem,
|
||||
HeadingFourItem,
|
||||
HeadingFiveItem,
|
||||
HeadingSixItem,
|
||||
} from "@plane/editor-core";
|
||||
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 { BubbleMenuItem } from ".";
|
||||
|
|
@ -23,18 +27,16 @@ interface NodeSelectorProps {
|
|||
|
||||
export const NodeSelector: FC<NodeSelectorProps> = ({ editor, isOpen, setIsOpen }) => {
|
||||
const items: BubbleMenuItem[] = [
|
||||
{
|
||||
name: "Text",
|
||||
icon: TextIcon,
|
||||
command: () => editor.chain().focus().clearNodes().run(),
|
||||
isActive: () => editor.isActive("paragraph") && !editor.isActive("bulletList") && !editor.isActive("orderedList"),
|
||||
},
|
||||
TextItem(editor),
|
||||
HeadingOneItem(editor),
|
||||
HeadingTwoItem(editor),
|
||||
HeadingThreeItem(editor),
|
||||
TodoListItem(editor),
|
||||
HeadingFourItem(editor),
|
||||
HeadingFiveItem(editor),
|
||||
HeadingSixItem(editor),
|
||||
BulletListItem(editor),
|
||||
NumberedListItem(editor),
|
||||
TodoListItem(editor),
|
||||
QuoteItem(editor),
|
||||
CodeItem(editor),
|
||||
];
|
||||
|
|
@ -58,7 +60,7 @@ export const NodeSelector: FC<NodeSelectorProps> = ({ editor, isOpen, setIsOpen
|
|||
</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">
|
||||
<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) => (
|
||||
<button
|
||||
key={item.name}
|
||||
|
|
@ -69,19 +71,17 @@ export const NodeSelector: FC<NodeSelectorProps> = ({ editor, isOpen, setIsOpen
|
|||
e.stopPropagation();
|
||||
}}
|
||||
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="rounded-sm border border-custom-border-300 p-1">
|
||||
<item.icon className="h-3 w-3" />
|
||||
</div>
|
||||
<item.icon className="size-3 flex-shrink-0" />
|
||||
<span>{item.name}</span>
|
||||
</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>
|
||||
))}
|
||||
</section>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue