fixed drag handles stuff

This commit is contained in:
Palanikannan1437 2024-03-28 16:11:18 +05:30
parent c5814acb09
commit 48254977a0
10 changed files with 229 additions and 14 deletions

View File

@ -76,7 +76,7 @@ export const useEditor = ({
setSavedSelection(editor.state.selection);
},
onUpdate: async ({ editor }) => {
setIsSubmitting?.("submitting");
// setIsSubmitting?.("submitting");
setShouldShowAlert?.(true);
onChange?.(editor.getJSON(), getTrimmedHTML(editor.getHTML()));
},

View File

@ -42,6 +42,9 @@
}
}
/* Custom list item styles */
/* Custom gap cursor styles */
.ProseMirror-gapcursor:after {
border-top: 1px solid rgb(var(--color-text-100)) !important;
}

View File

@ -9,7 +9,6 @@
border-collapse: collapse;
table-layout: fixed;
margin: 0;
margin-bottom: 1rem;
border: 2px solid rgba(var(--color-border-300));
width: 100%;
}

View File

@ -6,6 +6,10 @@ import TiptapUnderline from "@tiptap/extension-underline";
import StarterKit from "@tiptap/starter-kit";
import { Markdown } from "tiptap-markdown";
import BulletList from "@tiptap/extension-bullet-list";
import OrderedList from "@tiptap/extension-ordered-list";
import ListItem from "@tiptap/extension-list-item";
import { Table } from "src/ui/extensions/table/table";
import { TableCell } from "src/ui/extensions/table/table-cell/table-cell";
import { TableHeader } from "src/ui/extensions/table/table-header/table-header";
@ -42,17 +46,17 @@ export const CoreEditorExtensions = (
StarterKit.configure({
bulletList: {
HTMLAttributes: {
class: "list-disc list-outside leading-3",
class: "not-prose",
},
},
orderedList: {
HTMLAttributes: {
class: "list-decimal list-outside leading-3 -mt-2 -mb-2",
class: "not-prose",
},
},
listItem: {
HTMLAttributes: {
class: "leading-normal -mb-2",
class: "not-prose",
},
},
code: false,
@ -64,6 +68,10 @@ export const CoreEditorExtensions = (
width: 2,
},
}),
// BulletList,
// OrderedList,
// ListItem,
CustomQuoteExtension.configure({
HTMLAttributes: { className: "border-l-4 border-custom-border-300" },
}),

View File

@ -1,4 +1,4 @@
import { EditorContainer, EditorContentWrapper } from "@plane/editor-core";
import { cn, EditorContainer, EditorContentWrapper } from "@plane/editor-core";
import { Node } from "@tiptap/pm/model";
import { EditorView } from "@tiptap/pm/view";
import { Editor, ReactRenderer } from "@tiptap/react";
@ -15,6 +15,7 @@ import {
useFloating,
useInteractions,
} from "@floating-ui/react";
import BlockMenu from "../menu/block-menu";
type IPageRenderer = {
documentDetails: DocumentDetails;
@ -170,12 +171,21 @@ export const PageRenderer = (props: IPageRenderer) => {
/>
)}
<div className="flex relative h-full w-full flex-col pr-5 editor-renderer" onMouseOver={handleLinkHover}>
<EditorContainer hideDragHandle={hideDragHandle} editor={editor} editorClassNames={editorClassNames}>
<EditorContainer
hideDragHandle={hideDragHandle}
editor={editor}
editorClassNames={cn(editorClassNames, `h-[100000px]`)}
>
<EditorContentWrapper
tabIndex={tabIndex}
editor={editor}
editorContentCustomClassNames={editorContentCustomClassNames}
/>
{editor && editor.isEditable && (
<>
<BlockMenu editor={editor} />
</>
)}
</EditorContainer>
</div>
{isOpen && linkViewProps && coordinates && (

View File

@ -0,0 +1,126 @@
import { useCallback, useEffect, useRef } from "react";
import tippy, { Instance } from "tippy.js";
import { Editor } from "@tiptap/react";
import { CopyIcon, DeleteIcon } from "lucide-react";
interface BlockMenuProps {
editor: Editor;
}
export default function BlockMenu(props: BlockMenuProps) {
const { editor } = props;
const { view } = editor;
const menuRef = useRef<HTMLDivElement>(null);
const popup = useRef<Instance | null>(null);
const handleClickDragHandle = useCallback(
(event: MouseEvent) => {
const target = event.target as HTMLElement;
if (target.matches(".drag-handle-dots") || target.matches(".drag-handle-dot")) {
event.preventDefault();
popup.current?.setProps({
getReferenceClientRect: () => target.getBoundingClientRect(),
});
popup.current?.show();
return;
}
popup.current?.hide();
return;
},
[view]
);
const handleKeyDown = () => {
popup.current?.hide();
};
useEffect(() => {
if (menuRef.current) {
menuRef.current.remove();
menuRef.current.style.visibility = "visible";
popup.current = tippy(view.dom, {
getReferenceClientRect: null,
content: menuRef.current,
appendTo: "parent",
trigger: "manual",
interactive: true,
arrow: false,
placement: "left-start",
animation: "shift-away",
maxWidth: 500,
hideOnClick: true,
onShown: () => {
menuRef.current?.focus();
},
});
}
return () => {
popup.current?.destroy();
popup.current = null;
};
}, []);
useEffect(() => {
document.addEventListener("click", handleClickDragHandle);
document.addEventListener("keydown", handleKeyDown);
return () => {
document.removeEventListener("click", handleClickDragHandle);
document.addEventListener("keydown", handleKeyDown);
};
}, [handleClickDragHandle]);
return (
<div
ref={menuRef}
className="z-50 max-h-[300px] w-24 overflow-y-auto rounded-lg border border-gray-200 bg-white px-1 py-2 shadow-lg"
>
<button
className="flex w-full items-center gap-2 rounded-lg px-2 py-1 text-xs text-gray-600 hover:bg-gray-50"
onClick={(e) => {
editor.commands.deleteSelection();
popup.current?.hide();
e.preventDefault();
e.stopPropagation();
}}
>
<div className="grid place-items-center">
<DeleteIcon className="h-3 w-3" />
</div>
<p className="truncate">Delete</p>
</button>
<button
className="flex w-full items-center gap-2 rounded-lg px-2 py-1 text-xs text-gray-600 hover:bg-gray-50"
onClick={(e) => {
const { view } = editor;
const { state } = view;
const { selection } = state;
editor
.chain()
.insertContentAt(selection.to, selection.content().content.firstChild!.toJSON(), {
updateSelection: true,
})
.focus(selection.to + 1, { scrollIntoView: false }) // Focus the editor at the end
.run();
popup.current?.hide();
e.preventDefault();
e.stopPropagation();
}}
>
<div className="grid place-items-center">
<CopyIcon className="h-3 w-3" />
</div>
<p className="truncate">Duplicate</p>
</button>
</div>
);
}

View File

@ -27,7 +27,7 @@ type EditorBubbleMenuProps = {
editor: Editor;
uploadFile: UploadImage;
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void;
savedSelection?: Selection | null;
savedSelection: Selection | null;
};
export const FixedMenu = (props: EditorBubbleMenuProps) => {

View File

@ -50,9 +50,15 @@ function nodeDOMAtCoords(coords: { x: number; y: number }) {
(elem: Element) =>
elem.parentElement?.matches?.(".ProseMirror") ||
elem.matches(
["li", "p:not(:first-child)", "pre", "blockquote", "h1, h2, h3", "table", "[data-type=horizontalRule]"].join(
", "
)
[
"li",
"p:not(:first-child)",
"pre",
"blockquote",
"h1, h2, h3",
".tableWrapper",
"[data-type=horizontalRule]",
].join(", ")
)
);
}
@ -107,10 +113,14 @@ function DragHandle(options: DragHandleOptions) {
y: event.clientY,
});
console.log(node); // Log the node to see if it's correctly identified
if (!(node instanceof Element)) return;
const nodePos = nodePosAtDOM(node, view, options);
if (!nodePos) return;
console.log(nodePos); // Log the node position to see if it's correctly calculated
if (nodePos === null || nodePos === undefined) return;
view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, nodePos)));
}

View File

@ -13,9 +13,11 @@
}
.ProseMirror:not(.dragging) .ProseMirror-selectednode {
padding: 4px 0px;
outline: none !important;
cursor: grab;
background-color: #1f2937;
/* background-color: #1f2937; */
background-color: rgba(var(--color-primary-100), 0.2);
transition: background-color 0.2s;
box-shadow: none;
}
@ -26,6 +28,24 @@
transition: background-color 0.2s;
}
.ProseMirror img {
transition: filter 0.1s ease-in-out;
&:hover {
cursor: pointer;
filter: brightness(90%);
}
&.ProseMirror-selectednode {
outline: 3px solid #5abbf7;
filter: brightness(90%);
}
}
:not(.dragging) .ProseMirror-selectednode.tableWrapper {
padding: 4px 2px;
background-color: rgba(var(--color-primary-300), 0.1) !important;
box-shadow: rgba(var(--color-primary-100)) 0px 0px 0px 2px inset !important;
}
.drag-handle:active {
background-color: #4d4d4d;
transition: background-color 0.2s;
@ -66,3 +86,42 @@
background-color: rgba(var(--color-text-200));
border-radius: 50%;
}
/* block menu */
.blockMenu {
padding: 0.25rem;
display: flex;
flex-direction: column;
width: 200px;
gap: 0.25rem;
}
.blockMenu .action {
background: none;
display: flex;
align-items: center;
gap: 0.5rem;
border: none;
padding: 0.1rem;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
}
.blockMenu .action:disabled {
cursor: not-allowed;
}
.blockMenu .action:hover {
background-color: rgba(var(--color-text-200));
}
.blockMenu .action .actionIconContainer {
width: 1.75rem;
height: 1.75rem;
border: 1px solid red;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
}
.blockMenu .action .actionName {
font-family: inherit;
font-size: 0.95rem;
}

View File

@ -130,7 +130,7 @@ export class PageStore implements IPageStore {
});
});
},
{ delay: 3000 }
{ delay: 1500 }
);
const pageTitleDisposer = reaction(