Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import { cn } from "@plane/utils";
import { TextAlignItem } from "@/components/menus";
// types
import { TEditorCommands } from "@/types";
import { EditorStateType } from "./root";

type Props = {
editor: Editor;
onClose: () => void;
editorState: EditorStateType;
};

export const TextAlignmentSelector: React.FC<Props> = (props) => {
const { editor, onClose } = props;

const { editor, editorState } = props;
const menuItem = TextAlignItem(editor);

const textAlignmentOptions: {
Expand All @@ -32,10 +32,7 @@ export const TextAlignmentSelector: React.FC<Props> = (props) => {
menuItem.command({
alignment: "left",
}),
isActive: () =>
menuItem.isActive({
alignment: "left",
}),
isActive: () => editorState.left,
},
{
itemKey: "text-align",
Expand All @@ -45,10 +42,7 @@ export const TextAlignmentSelector: React.FC<Props> = (props) => {
menuItem.command({
alignment: "center",
}),
isActive: () =>
menuItem.isActive({
alignment: "center",
}),
isActive: () => editorState.center,
},
{
itemKey: "text-align",
Expand All @@ -58,10 +52,7 @@ export const TextAlignmentSelector: React.FC<Props> = (props) => {
menuItem.command({
alignment: "right",
}),
isActive: () =>
menuItem.isActive({
alignment: "right",
}),
isActive: () => editorState.right,
},
];

Expand All @@ -74,7 +65,6 @@ export const TextAlignmentSelector: React.FC<Props> = (props) => {
onClick={(e) => {
e.stopPropagation();
item.command();
onClose();
}}
className={cn(
"size-7 grid place-items-center rounded text-custom-text-300 hover:bg-custom-background-80 active:bg-custom-background-80 transition-colors",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
import { Dispatch, FC, SetStateAction } from "react";
import { Editor } from "@tiptap/react";
import { ALargeSmall, Ban } from "lucide-react";
import { Dispatch, FC, SetStateAction } from "react";
// plane utils
import { cn } from "@plane/utils";
// constants
import { COLORS_LIST } from "@/constants/common";
// helpers
import { BackgroundColorItem, TextColorItem } from "../menu-items";
import { EditorStateType } from "./root";

type Props = {
editor: Editor;
isOpen: boolean;
setIsOpen: Dispatch<SetStateAction<boolean>>;
editorState: EditorStateType;
};

export const BubbleMenuColorSelector: FC<Props> = (props) => {
const { editor, isOpen, setIsOpen } = props;
const { editor, isOpen, setIsOpen, editorState } = props;

const activeTextColor = COLORS_LIST.find((c) => TextColorItem(editor).isActive({ color: c.key }));
const activeBackgroundColor = COLORS_LIST.find((c) => BackgroundColorItem(editor).isActive({ color: c.key }));
const activeTextColor = editorState.color;
const activeBackgroundColor = editorState.backgroundColor;

return (
<div className="relative h-full">
Expand Down
126 changes: 86 additions & 40 deletions packages/editor/src/core/components/menus/bubble-menu/root.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,85 @@
import { FC, useEffect, useState } from "react";
import { BubbleMenu, BubbleMenuProps, Editor, isNodeSelection } from "@tiptap/react";
import { BubbleMenu, BubbleMenuProps, Editor, isNodeSelection, useEditorState } from "@tiptap/react";
import { FC, useEffect, useState, useRef } from "react";
// plane utils
import { cn } from "@plane/utils";
// components
import {
BackgroundColorItem,
BoldItem,
BubbleMenuColorSelector,
BubbleMenuLinkSelector,
BubbleMenuNodeSelector,
CodeItem,
ItalicItem,
StrikeThroughItem,
TextAlignItem,
TextColorItem,
UnderLineItem,
} from "@/components/menus";
// constants
import { COLORS_LIST } from "@/constants/common";
// extensions
import { isCellSelection } from "@/extensions/table/table/utilities/is-cell-selection";
// local components
import { TextAlignmentSelector } from "./alignment-selector";

type EditorBubbleMenuProps = Omit<BubbleMenuProps, "children">;

export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props: any) => {
// states
export interface EditorStateType {
code: boolean;
bold: boolean;
italic: boolean;
underline: boolean;
strike: boolean;
left: boolean;
right: boolean;
center: boolean;
color: { key: string; label: string; textColor: string; backgroundColor: string } | undefined;
backgroundColor:
| {
key: string;
label: string;
textColor: string;
backgroundColor: string;
}
| undefined;
}

export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props: { editor: Editor }) => {
const menuRef = useRef<HTMLDivElement>(null);
const [isNodeSelectorOpen, setIsNodeSelectorOpen] = useState(false);
const [isLinkSelectorOpen, setIsLinkSelectorOpen] = useState(false);
const [isColorSelectorOpen, setIsColorSelectorOpen] = useState(false);
const [isSelecting, setIsSelecting] = useState(false);

const basicFormattingOptions = props.editor.isActive("code")
? [CodeItem(props.editor)]
: [BoldItem(props.editor), ItalicItem(props.editor), UnderLineItem(props.editor), StrikeThroughItem(props.editor)];
const formattingItems = {
code: CodeItem(props.editor),
bold: BoldItem(props.editor),
italic: ItalicItem(props.editor),
underline: UnderLineItem(props.editor),
strike: StrikeThroughItem(props.editor),
textAlign: TextAlignItem(props.editor),
};

const editorState: EditorStateType = useEditorState({
editor: props.editor,
selector: ({ editor }: { editor: Editor }) => ({
code: formattingItems.code.isActive(),
bold: formattingItems.bold.isActive(),
italic: formattingItems.italic.isActive(),
underline: formattingItems.underline.isActive(),
strike: formattingItems.strike.isActive(),
left: formattingItems.textAlign.isActive({ alignment: "left" }),
right: formattingItems.textAlign.isActive({ alignment: "right" }),
center: formattingItems.textAlign.isActive({ alignment: "center" }),
color: COLORS_LIST.find((c) => TextColorItem(editor).isActive({ color: c.key })),
backgroundColor: COLORS_LIST.find((c) => BackgroundColorItem(editor).isActive({ color: c.key })),
}),
});

const basicFormattingOptions = editorState.code
? [formattingItems.code]
: [formattingItems.bold, formattingItems.italic, formattingItems.underline, formattingItems.strike];

const bubbleMenuProps: EditorBubbleMenuProps = {
...props,
Expand All @@ -51,6 +101,7 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props: any) => {
},
tippyOptions: {
moveTransition: "transform 0.15s ease-out",
duration: [300, 0],
onHidden: () => {
setIsNodeSelectorOpen(false);
setIsLinkSelectorOpen(false);
Expand All @@ -60,7 +111,9 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props: any) => {
};

useEffect(() => {
function handleMouseDown() {
function handleMouseDown(e: MouseEvent) {
if (menuRef.current?.contains(e.target as Node)) return;

function handleMouseMove() {
if (!props.editor.state.selection.empty) {
setIsSelecting(true);
Expand All @@ -70,7 +123,6 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props: any) => {

function handleMouseUp() {
setIsSelecting(false);

document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
}
Expand All @@ -84,27 +136,28 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props: any) => {
return () => {
document.removeEventListener("mousedown", handleMouseDown);
};
}, []);
}, [props.editor]);

return (
<BubbleMenu {...bubbleMenuProps}>
{!isSelecting && (
<div className="flex py-2 divide-x divide-custom-border-200 rounded-lg border border-custom-border-200 bg-custom-background-100 shadow-custom-shadow-rg">
<div
ref={menuRef}
className="flex py-2 divide-x divide-custom-border-200 rounded-lg border border-custom-border-200 bg-custom-background-100 shadow-custom-shadow-rg"
>
<div className="px-2">
{!props.editor.isActive("table") && (
<BubbleMenuNodeSelector
editor={props.editor!}
isOpen={isNodeSelectorOpen}
setIsOpen={() => {
setIsNodeSelectorOpen((prev) => !prev);
setIsLinkSelectorOpen(false);
setIsColorSelectorOpen(false);
}}
/>
)}
<BubbleMenuNodeSelector
editor={props.editor!}
isOpen={isNodeSelectorOpen}
setIsOpen={() => {
setIsNodeSelectorOpen((prev) => !prev);
setIsLinkSelectorOpen(false);
setIsColorSelectorOpen(false);
}}
/>
</div>
<div className="px-2">
{!props.editor.isActive("code") && (
{!editorState.code && (
<div className="px-2">
<BubbleMenuLinkSelector
editor={props.editor}
isOpen={isLinkSelectorOpen}
Expand All @@ -114,21 +167,22 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props: any) => {
setIsColorSelectorOpen(false);
}}
/>
)}
</div>
<div className="px-2">
{!props.editor.isActive("code") && (
</div>
)}
{!editorState.code && (
<div className="px-2">
<BubbleMenuColorSelector
editor={props.editor}
isOpen={isColorSelectorOpen}
editorState={editorState}
setIsOpen={() => {
setIsColorSelectorOpen((prev) => !prev);
setIsNodeSelectorOpen(false);
setIsLinkSelectorOpen(false);
}}
/>
)}
</div>
</div>
)}
<div className="flex gap-0.5 px-2">
{basicFormattingOptions.map((item) => (
<button
Expand All @@ -141,23 +195,15 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props: any) => {
className={cn(
"size-7 grid place-items-center rounded text-custom-text-300 hover:bg-custom-background-80 active:bg-custom-background-80 transition-colors",
{
"bg-custom-background-80 text-custom-text-100": item.isActive(""),
"bg-custom-background-80 text-custom-text-100": editorState[item.key],
}
)}
>
<item.icon className="size-4" />
</button>
))}
</div>
<TextAlignmentSelector
editor={props.editor}
onClose={() => {
const editor = props.editor as Editor;
if (!editor) return;
const pos = editor.state.selection.to;
editor.commands.setTextSelection(pos ?? 0);
}}
/>
<TextAlignmentSelector editor={props.editor} editorState={editorState} />
</div>
)}
</BubbleMenu>
Expand Down
4 changes: 2 additions & 2 deletions packages/editor/src/core/components/menus/menu-items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ export const UnderLineItem = (editor: Editor): EditorMenuItem<"underline"> => ({
icon: UnderlineIcon,
});

export const StrikeThroughItem = (editor: Editor): EditorMenuItem<"strikethrough"> => ({
key: "strikethrough",
export const StrikeThroughItem = (editor: Editor): EditorMenuItem<"strike"> => ({
key: "strike",
name: "Strikethrough",
isActive: () => editor?.isActive("strike"),
command: () => toggleStrike(editor),
Expand Down
4 changes: 2 additions & 2 deletions packages/editor/src/core/constants/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export const TEXT_ALIGNMENT_ITEMS: ToolbarMenuItem<"text-align">[] = [
},
];

const BASIC_MARK_ITEMS: ToolbarMenuItem<"bold" | "italic" | "underline" | "strikethrough">[] = [
const BASIC_MARK_ITEMS: ToolbarMenuItem<"bold" | "italic" | "underline" | "strike">[] = [
{
itemKey: "bold",
renderKey: "bold",
Expand All @@ -113,7 +113,7 @@ const BASIC_MARK_ITEMS: ToolbarMenuItem<"bold" | "italic" | "underline" | "strik
editors: ["lite", "document"],
},
{
itemKey: "strikethrough",
itemKey: "strike",
renderKey: "strikethrough",
name: "Strikethrough",
icon: Strikethrough,
Expand Down
2 changes: 1 addition & 1 deletion packages/editor/src/core/types/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export type TEditorCommands =
| "bold"
| "italic"
| "underline"
| "strikethrough"
| "strike"
| "bulleted-list"
| "numbered-list"
| "to-do-list"
Expand Down
4 changes: 2 additions & 2 deletions web/core/constants/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export const TEXT_ALIGNMENT_ITEMS: ToolbarMenuItem<"text-align">[] = [
},
];

const BASIC_MARK_ITEMS: ToolbarMenuItem<"bold" | "italic" | "underline" | "strikethrough">[] = [
const BASIC_MARK_ITEMS: ToolbarMenuItem<"bold" | "italic" | "underline" | "strike">[] = [
{
itemKey: "bold",
renderKey: "bold",
Expand All @@ -119,7 +119,7 @@ const BASIC_MARK_ITEMS: ToolbarMenuItem<"bold" | "italic" | "underline" | "strik
editors: ["lite", "document"],
},
{
itemKey: "strikethrough",
itemKey: "strike",
renderKey: "strikethrough",
name: "Strikethrough",
icon: Strikethrough,
Expand Down
Loading