diff --git a/apps/client/src/components/navbar/navbar.module.css b/apps/client/src/components/navbar/navbar.module.css index 12c6cc33..a3a0c2ab 100644 --- a/apps/client/src/components/navbar/navbar.module.css +++ b/apps/client/src/components/navbar/navbar.module.css @@ -6,7 +6,7 @@ padding-top: 0; display: flex; flex-direction: column; - /*border-right: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));*/ + border-right: rem(1px) solid light-dark('', var(--mantine-color-dark-4)); } .section { diff --git a/apps/client/src/features/editor/extensions/drag-handle.ts b/apps/client/src/features/editor/extensions/drag-handle.ts deleted file mode 100644 index 20b66d96..00000000 --- a/apps/client/src/features/editor/extensions/drag-handle.ts +++ /dev/null @@ -1,223 +0,0 @@ -import { Extension } from '@tiptap/core'; -import { NodeSelection, Plugin } from '@tiptap/pm/state'; -// @ts-ignore -import { __serializeForClipboard as serializeForClipboard, EditorView } from '@tiptap/pm/view'; - -export interface DragHandleOptions { - dragHandleWidth: number; -} - -function removeNode(node) { - node.parentNode.removeChild(node); -} - -function absoluteRect(node) { - const data = node.getBoundingClientRect(); - - return { - top: data.top, - left: data.left, - width: data.width, - }; -} - -function nodeDOMAtCoords(coords: { x: number; y: number }) { - return document - .elementsFromPoint(coords.x, coords.y) - .find( - (elem: HTMLElement) => - elem.parentElement?.matches?.('.ProseMirror') || - elem.matches( - [ - 'li', - 'p:not(:first-child)', - 'pre', - 'blockquote', - 'h1, h2, h3', - '[data-type=horizontalRule]', - ].join(', '), - ), - ); -} - -export function nodePosAtDOM(node: Element, view: EditorView) { - const boundingRect = node.getBoundingClientRect(); - - return view.posAtCoords({ - left: boundingRect.left + 1, - top: boundingRect.top + 1, - })?.inside; -} - -function DragHandle(options: DragHandleOptions) { - function handleDragStart(event: DragEvent, view: EditorView) { - view.focus(); - - if (!event.dataTransfer) return; - - const node = nodeDOMAtCoords({ - x: event.clientX + 50 + options.dragHandleWidth, - y: event.clientY, - }); - - if (!(node instanceof Element)) return; - - const nodePos = nodePosAtDOM(node, view); - if (!nodePos) return; - - view.dispatch( - view.state.tr.setSelection( - NodeSelection.create(view.state.doc, nodePos), - ), - ); - - const slice = view.state.selection.content(); - const { dom, text } = serializeForClipboard(view, slice); - - event.dataTransfer.clearData(); - event.dataTransfer.setData('text/html', dom.innerHTML); - event.dataTransfer.setData('text/plain', text); - event.dataTransfer.effectAllowed = 'copyMove'; - - event.dataTransfer.setDragImage(node, 0, 0); - - view.dragging = { slice, move: event.ctrlKey }; - } - - function handleClick(event: MouseEvent, view: EditorView) { - view.focus(); - view.dom.classList.remove('dragging'); - - const node = nodeDOMAtCoords({ - x: event.clientX + 50 + options.dragHandleWidth, - y: event.clientY, - }); - - if (!(node instanceof Element)) return; - - const nodePos = nodePosAtDOM(node, view); - if (!nodePos) return; - - view.dispatch( - view.state.tr.setSelection( - NodeSelection.create(view.state.doc, nodePos), - ), - ); - } - - let dragHandleElement: HTMLElement | null = null; - - function hideDragHandle() { - if (dragHandleElement) { - dragHandleElement.classList.add('hidden'); - } - } - - function showDragHandle() { - if (dragHandleElement) { - dragHandleElement.classList.remove('hidden'); - } - } - - // @ts-ignore - return new Plugin({ - view: (view) => { - dragHandleElement = document.createElement('div'); - dragHandleElement.draggable = true; - dragHandleElement.dataset.dragHandle = ''; - dragHandleElement.classList.add('drag-handle'); - dragHandleElement.addEventListener('dragstart', (e) => { - handleDragStart(e, view); - }); - dragHandleElement.addEventListener('click', (e) => { - handleClick(e, view); - }); - - hideDragHandle(); - - view?.dom?.parentElement?.appendChild(dragHandleElement); - - return { - destroy: () => { - dragHandleElement?.remove?.(); - dragHandleElement = null; - }, - }; - }, - props: { - handleDOMEvents: { - mousemove: (view, event) => { - if (!view.editable) { - return; - } - - const node = nodeDOMAtCoords({ - x: event.clientX + 50 + options.dragHandleWidth, - y: event.clientY, - }); - - if (!(node instanceof Element)) { - hideDragHandle(); - return; - } - - const compStyle = window.getComputedStyle(node); - const lineHeight = parseInt(compStyle.lineHeight, 10); - const paddingTop = parseInt(compStyle.paddingTop, 10); - - const rect = absoluteRect(node); - - rect.top += (lineHeight - 24) / 2; - rect.top += paddingTop; - // Li markers - if ( - node.matches('ul:not([data-type=taskList]) li, ol li') - ) { - rect.left -= options.dragHandleWidth; - } - rect.width = options.dragHandleWidth; - - if (!dragHandleElement) return; - - dragHandleElement.style.left = `${rect.left - rect.width}px`; - dragHandleElement.style.top = `${rect.top}px`; - showDragHandle(); - }, - keydown: () => { - hideDragHandle(); - }, - mousewheel: () => { - hideDragHandle(); - }, - // dragging class is used for CSS - dragstart: (view) => { - view.dom.classList.add('dragging'); - }, - drop: (view) => { - view.dom.classList.remove('dragging'); - }, - dragend: (view) => { - view.dom.classList.remove('dragging'); - }, - }, - }, - }); -} - -export interface DragAndDropOptions { -} - -// @ts-ignore -const DragAndDrop = Extension.create({ - name: 'dragAndDrop', - - addProseMirrorPlugins() { - return [ - DragHandle({ - dragHandleWidth: 24, - }), - ]; - }, -}); - -export default DragAndDrop; diff --git a/apps/client/src/features/editor/extensions/extensions.ts b/apps/client/src/features/editor/extensions/extensions.ts index 9816f94d..80c4ef48 100644 --- a/apps/client/src/features/editor/extensions/extensions.ts +++ b/apps/client/src/features/editor/extensions/extensions.ts @@ -9,7 +9,6 @@ import { Superscript } from "@tiptap/extension-superscript"; import SubScript from "@tiptap/extension-subscript"; import { Highlight } from "@tiptap/extension-highlight"; import { Typography } from "@tiptap/extension-typography"; -import DragAndDrop from "@/features/editor/extensions/drag-handle"; import { TextStyle } from "@tiptap/extension-text-style"; import { Color } from "@tiptap/extension-color"; import SlashCommand from "@/features/editor/extensions/slash-command"; @@ -17,6 +16,7 @@ import { Collaboration } from "@tiptap/extension-collaboration"; import { CollaborationCursor } from "@tiptap/extension-collaboration-cursor"; import { HocuspocusProvider } from "@hocuspocus/provider"; import { Comment, TrailingNode } from "@docmost/editor-ext"; +import GlobalDragHandle from "tiptap-extension-global-drag-handle"; export const mainExtensions = [ StarterKit.configure({ @@ -43,7 +43,7 @@ export const mainExtensions = [ }), Typography, TrailingNode, - DragAndDrop, + GlobalDragHandle, TextStyle, Color, SlashCommand, diff --git a/package.json b/package.json index 0233540d..9e44b2ec 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "@tiptap/starter-kit": "^2.2.4", "@tiptap/suggestion": "^2.2.4", "fractional-indexing-jittered": "^0.9.1", + "tiptap-extension-global-drag-handle": "^0.1.6", "y-indexeddb": "^9.0.12", "yjs": "^13.6.14" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 45f15afb..9a36ddd9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -101,6 +101,9 @@ importers: fractional-indexing-jittered: specifier: ^0.9.1 version: 0.9.1 + tiptap-extension-global-drag-handle: + specifier: ^0.1.6 + version: 0.1.6 y-indexeddb: specifier: ^9.0.12 version: 9.0.12(yjs@13.6.14) @@ -11035,6 +11038,10 @@ packages: '@popperjs/core': 2.11.8 dev: false + /tiptap-extension-global-drag-handle@0.1.6: + resolution: {integrity: sha512-kjRFd/glJGTHEglHPNWtML1iINuEBG4eWvWbNIuuXE2LbylFXYHKVgLvZgxilqXSBH/o+lKDEp7/iL3lKvnxGw==} + dev: false + /tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'}