diff --git a/packages/client/src/tiptap/core/extensions/dragable.ts b/packages/client/src/tiptap/core/extensions/dragable.ts new file mode 100644 index 00000000..d5b1c3b1 --- /dev/null +++ b/packages/client/src/tiptap/core/extensions/dragable.ts @@ -0,0 +1,130 @@ +import { Extension } from '@tiptap/core'; +import { Plugin } from 'prosemirror-state'; +import { NodeSelection } from 'prosemirror-state'; +import { __serializeForClipboard } from 'prosemirror-view'; + +function createRect(rect) { + if (rect == null) { + return null; + } + const newRect = { + left: rect.left + document.body.scrollLeft, + top: rect.top + document.body.scrollTop, + width: rect.width, + height: rect.height, + bottom: 0, + right: 0, + }; + newRect.bottom = newRect.top + newRect.height; + newRect.right = newRect.left + newRect.width; + return newRect; +} + +function absoluteRect(element) { + return createRect(element.getBoundingClientRect()); +} + +export const Dragable = Extension.create({ + name: 'dragable', + + addProseMirrorPlugins() { + let dropElement; + let currentNode; + let editorView; + const WIDTH = 24; + + function drag(e) { + if (!currentNode || currentNode.nodeType !== 1) return; + let pos = null; + const desc = editorView.docView.nearestDesc(currentNode, true); + + if (!(!desc || desc === editorView.docView)) { + pos = desc.posBefore; + } + + if (!pos) return; + + editorView.dispatch(editorView.state.tr.setSelection(NodeSelection.create(editorView.state.doc, pos))); + const slice = editorView.state.selection.content(); + const { dom, text } = __serializeForClipboard(editorView, slice); + e.dataTransfer.clearData(); + e.dataTransfer.setData('text/html', dom.innerHTML); + e.dataTransfer.setData('text/plain', text); + editorView.dragging = { slice, move: true }; + } + + return [ + new Plugin({ + view(view) { + editorView = view; + dropElement = document.createElement('div'); + dropElement.setAttribute('draggable', 'true'); + dropElement.className = 'drag-handler'; + dropElement.addEventListener('dragstart', drag); + view.dom.parentElement.appendChild(dropElement); + + return { + update(view) { + editorView = view; + }, + destroy() { + if (dropElement && dropElement.parentNode) { + dropElement.removeEventListener('dragstart', drag); + dropElement.parentNode.removeChild(dropElement); + } + }, + }; + }, + props: { + handleDOMEvents: { + drop() { + dropElement.style.opacity = 0; + setTimeout(() => { + const node = document.querySelector('.ProseMirror-hideselection'); + if (node) { + node.classList.remove('ProseMirror-hideselection'); + } + }, 50); + }, + mousemove(view, event) { + const coords = { left: event.clientX, top: event.clientY }; + const pos = view.posAtCoords(coords); + + if (!pos) return; + + let node = view.domAtPos(pos.pos); + + node = node.node; + + while (node && node.parentNode) { + if (node.parentNode?.classList?.contains?.('ProseMirror')) { + break; + } + node = node.parentNode; + } + + if (!node || !node.getBoundingClientRect) { + return; + } + + if (node?.classList?.contains('node-title') || node?.classList?.contains('node-table')) { + return; + } + + currentNode = node; + + const rect = absoluteRect(node); + const win = node.ownerDocument.defaultView; + rect.top += win.pageYOffset; + rect.left += win.pageXOffset; + rect.width = WIDTH + 'px'; + dropElement.style.left = rect.left - WIDTH + 'px'; + dropElement.style.top = rect.top + 6 + 'px'; + dropElement.style.opacity = 1; + }, + }, + }, + }), + ]; + }, +}); diff --git a/packages/client/src/tiptap/core/extensions/paragraph.ts b/packages/client/src/tiptap/core/extensions/paragraph.ts index c13cae25..7e25a7f4 100644 --- a/packages/client/src/tiptap/core/extensions/paragraph.ts +++ b/packages/client/src/tiptap/core/extensions/paragraph.ts @@ -1,12 +1,3 @@ import TitapParagraph from '@tiptap/extension-paragraph'; -import { ReactNodeViewRenderer } from '@tiptap/react'; -import { ParagraphWrapper } from '../wrappers/paragraph'; - -export const Paragraph = TitapParagraph.extend({ - draggable: true, - - addNodeView() { - return ReactNodeViewRenderer(ParagraphWrapper); - }, -}); +export const Paragraph = TitapParagraph.extend({}); diff --git a/packages/client/src/tiptap/core/styles/base.scss b/packages/client/src/tiptap/core/styles/base.scss index 91bd3c96..8a51a3e0 100644 --- a/packages/client/src/tiptap/core/styles/base.scss +++ b/packages/client/src/tiptap/core/styles/base.scss @@ -70,6 +70,7 @@ } p { + margin-top: 0.75rem; margin-bottom: 0; font-size: 1em; font-weight: normal; diff --git a/packages/client/src/tiptap/core/styles/drag.scss b/packages/client/src/tiptap/core/styles/drag.scss new file mode 100644 index 00000000..2841b243 --- /dev/null +++ b/packages/client/src/tiptap/core/styles/drag.scss @@ -0,0 +1,11 @@ +.drag-handler { + position: fixed; + width: 16px; + height: 16px; + cursor: grab; + opacity: 1; + background-image: url('data:image/svg+xml;charset=UTF-8,'); + background-repeat: no-repeat; + background-size: contain; + background-position: center; +} diff --git a/packages/client/src/tiptap/core/styles/index.scss b/packages/client/src/tiptap/core/styles/index.scss index 72c9d469..db29c736 100644 --- a/packages/client/src/tiptap/core/styles/index.scss +++ b/packages/client/src/tiptap/core/styles/index.scss @@ -17,3 +17,4 @@ @import './table.scss'; @import './title.scss'; @import './kityminder.scss'; +@import './drag.scss'; diff --git a/packages/client/src/tiptap/core/wrappers/dragable/index.module.scss b/packages/client/src/tiptap/core/wrappers/dragable/index.module.scss index 798e77de..8487d957 100644 --- a/packages/client/src/tiptap/core/wrappers/dragable/index.module.scss +++ b/packages/client/src/tiptap/core/wrappers/dragable/index.module.scss @@ -5,9 +5,9 @@ .dragHandle { position: absolute; top: 0.3rem; - left: -1.5rem; - width: 1rem; - height: 1rem; + left: -24px; + width: 16px; + height: 16px; cursor: grab; opacity: 0; background-image: url('data:image/svg+xml;charset=UTF-8,'); diff --git a/packages/client/src/tiptap/core/wrappers/paragraph/index.module.scss b/packages/client/src/tiptap/core/wrappers/paragraph/index.module.scss deleted file mode 100644 index 98cee0ac..00000000 --- a/packages/client/src/tiptap/core/wrappers/paragraph/index.module.scss +++ /dev/null @@ -1,3 +0,0 @@ -.paragraph { - margin-top: 0.75em; -} diff --git a/packages/client/src/tiptap/core/wrappers/paragraph/index.tsx b/packages/client/src/tiptap/core/wrappers/paragraph/index.tsx deleted file mode 100644 index cc706e85..00000000 --- a/packages/client/src/tiptap/core/wrappers/paragraph/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { NodeViewContent } from '@tiptap/react'; -import { useCallback } from 'react'; -import { DragableWrapper } from 'tiptap/core/wrappers/dragable'; - -import styles from './index.module.scss'; - -export const ParagraphWrapper = ({ editor }) => { - const prevent = useCallback((e) => { - e.prevntDefault(); - return false; - }, []); - - return ( - - - - ); -}; diff --git a/packages/client/src/tiptap/editor/collaboration/kit.ts b/packages/client/src/tiptap/editor/collaboration/kit.ts index 50d23f17..deba6268 100644 --- a/packages/client/src/tiptap/editor/collaboration/kit.ts +++ b/packages/client/src/tiptap/editor/collaboration/kit.ts @@ -16,6 +16,7 @@ import { Countdown } from 'tiptap/core/extensions/countdown'; import { Document } from 'tiptap/core/extensions/document'; import { DocumentChildren } from 'tiptap/core/extensions/document-children'; import { DocumentReference } from 'tiptap/core/extensions/document-reference'; +import { Dragable } from 'tiptap/core/extensions/dragable'; import { Dropcursor } from 'tiptap/core/extensions/dropcursor'; import { Emoji } from 'tiptap/core/extensions/emoji'; import { EventEmitter } from 'tiptap/core/extensions/event-emitter'; @@ -98,6 +99,7 @@ export const CollaborationKit = [ CodeBlock, Color, ColorHighlighter, + Dragable, Dropcursor, EventEmitter, Focus,