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);
|
||||
},
|
||||
onUpdate: async ({ editor }) => {
|
||||
setIsSubmitting?.("submitting");
|
||||
// setIsSubmitting?.("submitting");
|
||||
setShouldShowAlert?.(true);
|
||||
onChange?.(editor.getJSON(), getTrimmedHTML(editor.getHTML()));
|
||||
},
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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%;
|
||||
}
|
||||
|
@ -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" },
|
||||
}),
|
||||
|
@ -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 && (
|
||||
|
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;
|
||||
uploadFile: UploadImage;
|
||||
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void;
|
||||
savedSelection?: Selection | null;
|
||||
savedSelection: Selection | null;
|
||||
};
|
||||
|
||||
export const FixedMenu = (props: EditorBubbleMenuProps) => {
|
||||
|
@ -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)));
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -130,7 +130,7 @@ export class PageStore implements IPageStore {
|
||||
});
|
||||
});
|
||||
},
|
||||
{ delay: 3000 }
|
||||
{ delay: 1500 }
|
||||
);
|
||||
|
||||
const pageTitleDisposer = reaction(
|
||||
|
Loading…
Reference in New Issue
Block a user