mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
fixed drag handles stuff
This commit is contained in:
parent
c5814acb09
commit
48254977a0
@ -76,7 +76,7 @@ export const useEditor = ({
|
|||||||
setSavedSelection(editor.state.selection);
|
setSavedSelection(editor.state.selection);
|
||||||
},
|
},
|
||||||
onUpdate: async ({ editor }) => {
|
onUpdate: async ({ editor }) => {
|
||||||
setIsSubmitting?.("submitting");
|
// setIsSubmitting?.("submitting");
|
||||||
setShouldShowAlert?.(true);
|
setShouldShowAlert?.(true);
|
||||||
onChange?.(editor.getJSON(), getTrimmedHTML(editor.getHTML()));
|
onChange?.(editor.getJSON(), getTrimmedHTML(editor.getHTML()));
|
||||||
},
|
},
|
||||||
|
@ -42,6 +42,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Custom list item styles */
|
||||||
|
|
||||||
|
/* Custom gap cursor styles */
|
||||||
.ProseMirror-gapcursor:after {
|
.ProseMirror-gapcursor:after {
|
||||||
border-top: 1px solid rgb(var(--color-text-100)) !important;
|
border-top: 1px solid rgb(var(--color-text-100)) !important;
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
margin-bottom: 1rem;
|
|
||||||
border: 2px solid rgba(var(--color-border-300));
|
border: 2px solid rgba(var(--color-border-300));
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,10 @@ import TiptapUnderline from "@tiptap/extension-underline";
|
|||||||
import StarterKit from "@tiptap/starter-kit";
|
import StarterKit from "@tiptap/starter-kit";
|
||||||
import { Markdown } from "tiptap-markdown";
|
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 { Table } from "src/ui/extensions/table/table";
|
||||||
import { TableCell } from "src/ui/extensions/table/table-cell/table-cell";
|
import { TableCell } from "src/ui/extensions/table/table-cell/table-cell";
|
||||||
import { TableHeader } from "src/ui/extensions/table/table-header/table-header";
|
import { TableHeader } from "src/ui/extensions/table/table-header/table-header";
|
||||||
@ -42,17 +46,17 @@ export const CoreEditorExtensions = (
|
|||||||
StarterKit.configure({
|
StarterKit.configure({
|
||||||
bulletList: {
|
bulletList: {
|
||||||
HTMLAttributes: {
|
HTMLAttributes: {
|
||||||
class: "list-disc list-outside leading-3",
|
class: "not-prose",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
orderedList: {
|
orderedList: {
|
||||||
HTMLAttributes: {
|
HTMLAttributes: {
|
||||||
class: "list-decimal list-outside leading-3 -mt-2 -mb-2",
|
class: "not-prose",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
listItem: {
|
listItem: {
|
||||||
HTMLAttributes: {
|
HTMLAttributes: {
|
||||||
class: "leading-normal -mb-2",
|
class: "not-prose",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
code: false,
|
code: false,
|
||||||
@ -64,6 +68,10 @@ export const CoreEditorExtensions = (
|
|||||||
width: 2,
|
width: 2,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
// BulletList,
|
||||||
|
// OrderedList,
|
||||||
|
// ListItem,
|
||||||
|
|
||||||
CustomQuoteExtension.configure({
|
CustomQuoteExtension.configure({
|
||||||
HTMLAttributes: { className: "border-l-4 border-custom-border-300" },
|
HTMLAttributes: { className: "border-l-4 border-custom-border-300" },
|
||||||
}),
|
}),
|
||||||
|
@ -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 { Node } from "@tiptap/pm/model";
|
||||||
import { EditorView } from "@tiptap/pm/view";
|
import { EditorView } from "@tiptap/pm/view";
|
||||||
import { Editor, ReactRenderer } from "@tiptap/react";
|
import { Editor, ReactRenderer } from "@tiptap/react";
|
||||||
@ -15,6 +15,7 @@ import {
|
|||||||
useFloating,
|
useFloating,
|
||||||
useInteractions,
|
useInteractions,
|
||||||
} from "@floating-ui/react";
|
} from "@floating-ui/react";
|
||||||
|
import BlockMenu from "../menu/block-menu";
|
||||||
|
|
||||||
type IPageRenderer = {
|
type IPageRenderer = {
|
||||||
documentDetails: DocumentDetails;
|
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}>
|
<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
|
<EditorContentWrapper
|
||||||
tabIndex={tabIndex}
|
tabIndex={tabIndex}
|
||||||
editor={editor}
|
editor={editor}
|
||||||
editorContentCustomClassNames={editorContentCustomClassNames}
|
editorContentCustomClassNames={editorContentCustomClassNames}
|
||||||
/>
|
/>
|
||||||
|
{editor && editor.isEditable && (
|
||||||
|
<>
|
||||||
|
<BlockMenu editor={editor} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</EditorContainer>
|
</EditorContainer>
|
||||||
</div>
|
</div>
|
||||||
{isOpen && linkViewProps && coordinates && (
|
{isOpen && linkViewProps && coordinates && (
|
||||||
|
126
packages/editor/document-editor/src/ui/menu/block-menu.tsx
Normal file
126
packages/editor/document-editor/src/ui/menu/block-menu.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
@ -27,7 +27,7 @@ type EditorBubbleMenuProps = {
|
|||||||
editor: Editor;
|
editor: Editor;
|
||||||
uploadFile: UploadImage;
|
uploadFile: UploadImage;
|
||||||
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void;
|
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void;
|
||||||
savedSelection?: Selection | null;
|
savedSelection: Selection | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FixedMenu = (props: EditorBubbleMenuProps) => {
|
export const FixedMenu = (props: EditorBubbleMenuProps) => {
|
||||||
|
@ -50,9 +50,15 @@ function nodeDOMAtCoords(coords: { x: number; y: number }) {
|
|||||||
(elem: Element) =>
|
(elem: Element) =>
|
||||||
elem.parentElement?.matches?.(".ProseMirror") ||
|
elem.parentElement?.matches?.(".ProseMirror") ||
|
||||||
elem.matches(
|
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,
|
y: event.clientY,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(node); // Log the node to see if it's correctly identified
|
||||||
|
|
||||||
if (!(node instanceof Element)) return;
|
if (!(node instanceof Element)) return;
|
||||||
|
|
||||||
const nodePos = nodePosAtDOM(node, view, options);
|
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.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, nodePos)));
|
||||||
}
|
}
|
||||||
|
@ -13,9 +13,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ProseMirror:not(.dragging) .ProseMirror-selectednode {
|
.ProseMirror:not(.dragging) .ProseMirror-selectednode {
|
||||||
|
padding: 4px 0px;
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
background-color: #1f2937;
|
/* background-color: #1f2937; */
|
||||||
|
background-color: rgba(var(--color-primary-100), 0.2);
|
||||||
transition: background-color 0.2s;
|
transition: background-color 0.2s;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
@ -26,6 +28,24 @@
|
|||||||
transition: background-color 0.2s;
|
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 {
|
.drag-handle:active {
|
||||||
background-color: #4d4d4d;
|
background-color: #4d4d4d;
|
||||||
transition: background-color 0.2s;
|
transition: background-color 0.2s;
|
||||||
@ -66,3 +86,42 @@
|
|||||||
background-color: rgba(var(--color-text-200));
|
background-color: rgba(var(--color-text-200));
|
||||||
border-radius: 50%;
|
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;
|
||||||
|
}
|
||||||
|
@ -130,7 +130,7 @@ export class PageStore implements IPageStore {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
{ delay: 3000 }
|
{ delay: 1500 }
|
||||||
);
|
);
|
||||||
|
|
||||||
const pageTitleDisposer = reaction(
|
const pageTitleDisposer = reaction(
|
||||||
|
Loading…
Reference in New Issue
Block a user