From a8bdf56c787015fe8eac59ccbfe2a858775ddb1c Mon Sep 17 00:00:00 2001 From: fantasticit Date: Sun, 16 Feb 2025 13:20:16 +0800 Subject: [PATCH] fix: fix js error --- packages/client/next.config.js | 2 +- .../admin/system-config/ad/index.tsx | 46 +- .../src/tiptap/core/extensions/dragable.ts | 667 ++++++++++-------- .../src/tiptap/core/extensions/paste.ts | 138 ++-- .../client/src/tiptap/core/styles/drag.scss | 40 +- .../client/src/tiptap/core/styles/var.scss | 2 + 6 files changed, 478 insertions(+), 417 deletions(-) diff --git a/packages/client/next.config.js b/packages/client/next.config.js index ea17d882..35dd3196 100644 --- a/packages/client/next.config.js +++ b/packages/client/next.config.js @@ -23,7 +23,7 @@ const nextConfig = semi({ env: { SERVER_API_URL: config.client.apiUrl, COLLABORATION_API_URL: config.client.collaborationUrl, - ENABLE_OSS_S3: config.oss.s3.enable, + ENABLE_OSS_S3: config.oss.s3?.enable, DNS_PREFETCH: (config.client.dnsPrefetch || '').split(' '), SITE_URL: config.client.siteUrl, }, diff --git a/packages/client/src/components/admin/system-config/ad/index.tsx b/packages/client/src/components/admin/system-config/ad/index.tsx index d5434bf4..874b44f3 100644 --- a/packages/client/src/components/admin/system-config/ad/index.tsx +++ b/packages/client/src/components/admin/system-config/ad/index.tsx @@ -47,6 +47,28 @@ export const Ad = () => { const $form = useRef(); const [currentCover, setCurrentCover] = useState(''); + const columns = useMemo(() => { + return [ + ...columns, + { + title: '操作', + dataIndex: 'id', + render: (id) => { + return ( + + ); + }, + }, + ]; + }, [deleteAd]); + const setCover = useCallback((url) => { $form.current.setValue('cover', url); setCurrentCover(url); @@ -111,29 +133,7 @@ export const Ad = () => { - { - return ( - - ); - }, - }, - ]} - dataSource={data} - pagination={false} - /> +
)} /> diff --git a/packages/client/src/tiptap/core/extensions/dragable.ts b/packages/client/src/tiptap/core/extensions/dragable.ts index 30d8bdef..6a93e420 100644 --- a/packages/client/src/tiptap/core/extensions/dragable.ts +++ b/packages/client/src/tiptap/core/extensions/dragable.ts @@ -1,341 +1,396 @@ -import ReactDOM from 'react-dom'; +import type { Editor } from '@tiptap/core'; +import { Extension, findParentNodeClosestToPos } from '@tiptap/core'; -import { Extension } from '@tiptap/core'; -import { safePos } from 'tiptap/prose-utils'; -import { ActiveNode, removePossibleTable, selectAncestorNodeByDom } from 'tiptap/prose-utils'; +import type { Node } from 'prosemirror-model'; +import { Fragment, Slice } from 'prosemirror-model'; +import { NodeSelection, Plugin, PluginKey, TextSelection } from 'prosemirror-state'; +import type { EditorView } from 'prosemirror-view'; +// @ts-expect-error +import { __serializeForClipboard } from 'prosemirror-view'; -import { - NodeSelection, - Plugin as PMPlugin, - PluginKey as PMPluginKey, - Selection, - TextSelection, -} from 'prosemirror-state'; -import { findParentNodeClosestToPos } from 'prosemirror-utils'; -import { __serializeForClipboard, Decoration, DecorationSet, EditorView } from 'prosemirror-view'; +function findNodeByUuid(editor: Editor, uuid: string) { + let target = null; + let pos = -1; -const DragablePluginKey = new PMPluginKey('dragable'); - -export const Dragable = Extension.create({ - name: 'dragable', - - // @ts-ignore - addProseMirrorPlugins() { - let editorView: EditorView; - let dragHandleDOM: HTMLElement; - let activeNode: ActiveNode | null; - let activeSelection: Selection | null; - let dragging = false; - let mouseleaveTimer = null; - - const createDragHandleDOM = () => { - const dom = document.createElement('div'); - dom.className = 'dragable'; - dom.draggable = true; - dom.setAttribute('data-drag-handle', 'true'); - - return dom; - }; - - const showDragHandleDOM = () => { - dragHandleDOM?.classList?.add('show'); - dragHandleDOM?.classList?.remove('hide'); - }; - - const hideDragHandleDOM = () => { - dragHandleDOM?.classList?.remove('show'); - dragHandleDOM?.classList?.remove('active'); - dragHandleDOM?.classList?.add('hide'); - }; - - const renderDragHandleDOM = (view: EditorView, referenceRectDOM: HTMLElement) => { - const root = view.dom.parentElement; - - if (!root) return; - - const targetNodeRect = referenceRectDOM.getBoundingClientRect(); - const rootRect = root.getBoundingClientRect(); - const handleRect = dragHandleDOM.getBoundingClientRect(); - - let offsetX = -5; - - if (referenceRectDOM.tagName === 'LI') { - offsetX = referenceRectDOM.getAttribute('data-checked') ? -3 : -16; + if (editor && !editor.isDestroyed) { + editor.state.doc.descendants((node, p) => { + if (node.attrs.uuid === uuid) { + target = node; + pos = p; + return false; } + }); + } - const left = targetNodeRect.left - rootRect.left - handleRect.width + offsetX; - const top = targetNodeRect.top - rootRect.top + handleRect.height / 2 + root.scrollTop; + return { node: target, pos }; +} - const offsetLeft = 0; +export interface GlobalDragHandleOptions { + dragHandleWidth: number; + scrollTreshold: number; + dragHandleSelector?: string; +} - dragHandleDOM.style.left = `${left + offsetLeft}px`; - dragHandleDOM.style.top = `${top - 2}px`; +function absoluteRect(node: Element) { + const data = node.getBoundingClientRect(); - showDragHandleDOM(); - }; + return { + top: data.top, + left: data.left, + width: data.width, + }; +} - const handleMouseEnter = () => { - if (!activeNode) return null; +function nodeDOMAtCoords(coords: { x: number; y: number }) { + return document + .elementsFromPoint(coords.x, coords.y) + .find( + (elem: Element) => + elem.parentElement?.matches?.('.ProseMirror') || + elem.matches(['li', 'p:not(:first-child)', 'pre', 'blockquote', 'h1, h2, h3, h4, h5, h6'].join(', ')) + ); +} - clearTimeout(mouseleaveTimer); - showDragHandleDOM(); - }; +function nodePosAtDOM(node: Element, view: EditorView, options: GlobalDragHandleOptions) { + const boundingRect = node.getBoundingClientRect(); - const handleMouseLeave = () => { - if (!activeNode) return null; - hideDragHandleDOM(); - }; + return view.posAtCoords({ + left: boundingRect.left + 50 + options.dragHandleWidth, + top: boundingRect.top + 1, + })?.inside; +} - const handleMouseDown = () => { - if (!activeNode) return null; +function calcNodePos(pos: number, view: EditorView) { + const $pos = view.state.doc.resolve(pos); + if ($pos.depth > 1) return $pos.before($pos.depth); + return pos; +} - if (NodeSelection.isSelectable(activeNode.node)) { - const nodeSelection = NodeSelection.create(editorView.state.doc, activeNode.$pos.pos - activeNode.offset); - editorView.dispatch(editorView.state.tr.setSelection(nodeSelection)); - editorView.focus(); - activeSelection = nodeSelection; - return nodeSelection; +export function DragHandlePlugin(options: GlobalDragHandleOptions & { pluginKey: string; editor: Editor }) { + const editor = options.editor; + let listType = ''; + let dragHandleElement: HTMLElement | null = null; + + let activeNode: { node: Node; pos: number } | null = null; + + const handleDragStart = (event: DragEvent) => { + const view = options.editor.view; + + view.focus(); + + if (!event.dataTransfer) return; + + const node = nodeDOMAtCoords({ + x: event.clientX + 50 + options.dragHandleWidth, + y: event.clientY, + }); + + if (!(node instanceof Element)) return; + + if (activeNode && NodeSelection.isSelectable(activeNode.node)) { + const selection = NodeSelection.create(view.state.doc, activeNode.pos); + view.dispatch(view.state.tr.setSelection(selection)); + } else { + let draggedNodePos = nodePosAtDOM(node, view, options); + if (draggedNodePos == null || draggedNodePos < 0) return; + draggedNodePos = calcNodePos(draggedNodePos, view); + + const { from, to } = view.state.selection; + const diff = from - to; + + const fromSelectionPos = calcNodePos(from, view); + let differentNodeSelected = false; + + const nodePos = view.state.doc.resolve(fromSelectionPos); + + // Check if nodePos points to the top level node + if (nodePos.node().type.name === 'doc') { + differentNodeSelected = true; + } else { + const nodeSelection = NodeSelection.create(view.state.doc, nodePos.before()); + + // Check if the node where the drag event started is part of the current selection + differentNodeSelected = !( + draggedNodePos + 1 >= nodeSelection.$from.pos && draggedNodePos <= nodeSelection.$to.pos + ); } + let selection = view.state.selection; + if (!differentNodeSelected && diff !== 0 && !(view.state.selection instanceof NodeSelection)) { + const endSelection = NodeSelection.create(view.state.doc, to - 1); + selection = TextSelection.create(view.state.doc, draggedNodePos, endSelection.$to.pos); + } else { + selection = NodeSelection.create(view.state.doc, draggedNodePos); - return null; - }; - - const handleMouseUp = () => { - if (!dragging) return; - - dragging = false; - activeSelection = null; - activeNode = null; - }; - - const handleDragStart = (event) => { - dragging = true; - if (event.dataTransfer && activeSelection) { - const slice = activeSelection.content(); - event.dataTransfer.effectAllowed = 'copyMove'; - const { dom, text } = __serializeForClipboard(editorView, slice); - event.dataTransfer.clearData(); - event.dataTransfer.setData('text/html', dom.innerHTML); - event.dataTransfer.setData('text/plain', text); - event.dataTransfer.setDragImage(activeNode?.el as any, 0, 0); - - editorView.dragging = { - slice, - move: true, - }; + // select complete table instead of just a row + if ((selection as NodeSelection).node.type.name === 'tableRow') { + const $pos = view.state.doc.resolve(selection.from); + selection = NodeSelection.create(view.state.doc, $pos.before()); + } } - }; + view.dispatch(view.state.tr.setSelection(selection)); + } - return [ - new PMPlugin({ - key: DragablePluginKey, - view: (view) => { - if (view.editable) { - dragHandleDOM = createDragHandleDOM(); - dragHandleDOM.addEventListener('mouseenter', handleMouseEnter); - dragHandleDOM.addEventListener('mouseleave', handleMouseLeave); - dragHandleDOM.addEventListener('mousedown', handleMouseDown); - dragHandleDOM.addEventListener('mouseup', handleMouseUp); - dragHandleDOM.addEventListener('dragstart', handleDragStart); - view.dom.parentNode?.appendChild(dragHandleDOM); - view.dom.parentNode.style = 'position: relative;'; + // If the selected node is a list item, we need to save the type of the wrapping list e.g. OL or UL + if (view.state.selection instanceof NodeSelection && view.state.selection.node.type.name === 'listItem') { + listType = node.parentElement!.tagName.toUpperCase(); + } + + 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: true }; + }; + + const hideDragHandle = () => { + if (dragHandleElement) { + dragHandleElement.classList.add('hide'); + } + activeNode = null; + }; + + const showDragHandle = () => { + if (dragHandleElement) { + dragHandleElement.classList.remove('hide'); + } + }; + + const onDragHandleDrag = (e: DragEvent) => { + hideDragHandle(); + const scrollY = window.scrollY; + if (e.clientY < options.scrollTreshold) { + window.scrollTo({ top: scrollY - 30, behavior: 'smooth' }); + } else if (window.innerHeight - e.clientY < options.scrollTreshold) { + window.scrollTo({ top: scrollY + 30, behavior: 'smooth' }); + } + }; + + const showDragHandleAtDOM = (node: HTMLElement | undefined) => { + const notDragging = node?.closest('.not-draggable'); + + if (!(node instanceof Element) || node.matches('ul, ol') || notDragging) { + hideDragHandle(); + return; + } + + const compStyle = window.getComputedStyle(node); + const parsedLineHeight = Number.parseInt(compStyle.lineHeight, 10); + const lineHeight = Number.isNaN(parsedLineHeight) ? Number.parseInt(compStyle.fontSize) * 1.2 : parsedLineHeight; + const paddingTop = Number.parseInt(compStyle.paddingTop, 10); + + const rect = absoluteRect(node); + rect.top += (lineHeight - 24) / 2; + rect.top += paddingTop; + 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 - 12}px`; + dragHandleElement.style.top = `${rect.top}px`; + showDragHandle(); + }; + + const onEditorUpdate = () => { + if (!editor.isEditable) { + hideDragHandle(); + } + }; + + // const onSelectionUpdate = () => { + // if (!editor.isEditable) + // return + + // const maybeNode = findParentNodeClosestToPos(editor.state.selection.$anchor, node => !node.isInline) + + // if (!maybeNode) + // return + + // const maybeDOM = findDomRefAtPos(maybeNode.pos, editor.view.domAtPos.bind(editor.view)) + + // if (maybeDOM) { + // showDragHandleAtDOM(maybeDOM as HTMLElement) + // activeNode = { node: maybeNode.node, pos: maybeNode.pos } + // } + // } + + const callShowDraggableMenu = () => { + if (!dragHandleElement) return; + showDragHandle(); + }; + + return new Plugin({ + key: new PluginKey(options.pluginKey), + view: (view) => { + dragHandleElement = document.createElement('div'); + dragHandleElement.draggable = true; + dragHandleElement.dataset.dragHandle = ''; + dragHandleElement.classList.add('drag-handle'); + dragHandleElement.addEventListener('dragstart', handleDragStart); + dragHandleElement.addEventListener('drag', onDragHandleDrag); + dragHandleElement.addEventListener('mousedown', callShowDraggableMenu); + dragHandleElement.addEventListener('mouseout', hideDragHandle); + + hideDragHandle(); + view?.dom?.parentElement?.appendChild(dragHandleElement); + // editor.on('selectionUpdate', onSelectionUpdate) + editor.on('update', onEditorUpdate); + + return { + destroy: () => { + dragHandleElement?.removeEventListener('drag', onDragHandleDrag); + dragHandleElement?.removeEventListener('dragstart', handleDragStart); + dragHandleElement?.removeEventListener('mousedown', callShowDraggableMenu); + dragHandleElement?.remove?.(); + dragHandleElement = null; + // editor.off('selectionUpdate', onSelectionUpdate) + editor.off('update', onEditorUpdate); + }, + }; + }, + props: { + handleDOMEvents: { + mousemove: (view, event) => { + if (!view.editable) { + return; } - return { - update(view) { - editorView = view; - }, - destroy: () => { - if (!dragHandleDOM) return; - - clearTimeout(mouseleaveTimer); - ReactDOM.unmountComponentAtNode(dragHandleDOM); - dragHandleDOM.removeEventListener('mouseenter', handleMouseEnter); - dragHandleDOM.removeEventListener('mouseleave', handleMouseLeave); - dragHandleDOM.removeEventListener('mousedown', handleMouseDown); - dragHandleDOM.removeEventListener('mouseup', handleMouseUp); - dragHandleDOM.removeEventListener('dragstart', handleDragStart); - dragHandleDOM.remove(); - }, + const coords = { + x: event.clientX + 50 + options.dragHandleWidth, + y: event.clientY, }; - }, - props: { - // @ts-ignore - handleDOMEvents: { - drop: (view, event: DragEvent) => { - if (!view.editable || !dragHandleDOM) return false; - if (!activeSelection) return false; + const maybePos = editor.view.posAtCoords({ left: coords.x, top: coords.y }); + let dom = nodeDOMAtCoords(coords) as HTMLElement | null; - const eventPos = view.posAtCoords({ - left: event.clientX, - top: event.clientY, - }); + while (dom && dom.nodeType === 3) { + dom = dom.parentElement; + } - setTimeout(() => { - if (activeSelection) { - [ - 'ProseMirror-selectednode', - 'ProseMirror-selectedblocknode-dragable', - 'ProseMirror-selectedblocknode-normal', - ].forEach((cls) => { - (view.dom as HTMLElement).querySelectorAll(`.${cls}`).forEach((dom) => dom.classList.remove(cls)); - }); - const noneSelection = new TextSelection( - view.state.doc.resolve(safePos(view.state, eventPos?.pos ?? 0)) - ); - view.dispatch(view.state.tr.setSelection(noneSelection)); - this.editor.commands.blur(); + if (dom?.closest(`[data-node-view-wrapper]`)) { + dom = dom?.closest(`[data-node-view-wrapper]`); + } - activeSelection = null; - activeNode = null; - } - }, 100); + if (dom?.closest(`.columns`)) { + dom = dom?.closest(`.columns`); + } - if (!eventPos) { - return true; - } + if (dom?.closest(`table`)) { + dom = dom?.closest(`table`); + } - const maybeTitle = findParentNodeClosestToPos( - view.state.doc.resolve(safePos(this.editor.state, eventPos.pos)), - (node) => node.type.name === 'title' - ); + if (!dom) { + hideDragHandle(); + return false; + } - // 不允许在 title 处放置 - if (eventPos.pos === 0 || maybeTitle) { - return true; - } + let maybeNode; - if (dragging) { - const tr = removePossibleTable(view, event); - - dragging = false; - - if (tr) { - view.dispatch(tr); - event.preventDefault(); - return true; - } - } + if (dom.getAttribute('data-uuid')) { + maybeNode = findNodeByUuid(editor, dom.getAttribute('data-uuid')!); + } + if (!maybeNode) { + const p = editor.view.posAtDOM(dom, 1); + if (!p || p < 0) { + hideDragHandle(); return false; - }, - mousemove: (view, event) => { - if (!view.editable || !dragHandleDOM) return false; + } + const maybeAfter = editor.state.doc.resolve(p)?.nodeBefore; - const coords = { left: event.clientX, top: event.clientY }; - const pos = view.posAtCoords(coords); - - if (!pos || !pos.pos) return false; - - let dom = view.nodeDOM(pos.pos) || view.domAtPos(pos.pos)?.node || event.target; - - const maybeTaskItemOrListItem = findParentNodeClosestToPos(view.state.doc.resolve(pos.pos), (node) => - ['taskItem', 'listItem'].some((name) => name === node.type.name) - ); - - if (!dom) { - if (dragging) return false; - hideDragHandleDOM(); - return false; - } - - while (dom && dom.nodeType === 3) { - dom = dom.parentElement; - } - - // 选中列表项 - if (maybeTaskItemOrListItem) { - while (dom && dom.tagName !== 'LI') { - dom = dom.parentElement; - } - } - - if (dom.tagName === 'LI') { - if (dom?.parentElement?.childElementCount === 1) { - return false; - } - } - - // 不允许选中整个列表 - if (dom.tagName === 'UL' || dom.tagName === 'OL') { - return false; - } - - try { - let maybeReactRenderer: HTMLElement | null = dom; - - while (maybeReactRenderer && !maybeReactRenderer.classList?.contains('react-renderer')) { - maybeReactRenderer = maybeReactRenderer.parentElement; - } - - if (maybeReactRenderer && !maybeReactRenderer?.classList?.contains('node-columns')) { - dom = maybeReactRenderer; - } - } catch (e) { - // - } - - if (!(dom instanceof Element)) { - if (dragging) return false; - hideDragHandleDOM(); - return false; - } - - const result = selectAncestorNodeByDom(dom, view); - - if ( - !result || - result.node.type.name === 'doc' || - result.node.type.name === 'title' || - result.node.type.name === 'tableOfContents' - ) { - if (dragging) return false; - hideDragHandleDOM(); - return false; - } - - activeNode = result; - renderDragHandleDOM(view, result.el); - return false; - }, - keydown: () => { - if (!editorView.editable || !dragHandleDOM) return false; - hideDragHandleDOM(); - return false; - }, - mouseleave: () => { - clearTimeout(mouseleaveTimer); - mouseleaveTimer = setTimeout(() => { - hideDragHandleDOM(); - }, 400); - return false; - }, - }, - }, - }), - new PMPlugin({ - key: new PMPluginKey('AncestorDragablePluginFocusKey'), - props: { - decorations(state) { - const usingActiveSelection = !!activeSelection; - const selection = state.selection; - - if (selection instanceof NodeSelection) { - const { from, to } = selection; - - return DecorationSet.create(state.doc, [ - Decoration.node(safePos(state, from), safePos(state, to), { - class: usingActiveSelection - ? 'ProseMirror-selectedblocknode-dragable' - : 'ProseMirror-selectedblocknode-normal', - }), - ]); + if (maybeAfter && !maybeAfter.isInline) { + maybeNode = { node: maybeAfter, pos: p }; } - return DecorationSet.empty; - }, + if (!maybeNode && maybePos?.pos) { + maybeNode = findParentNodeClosestToPos(editor.state.doc.resolve(maybePos?.pos), (node) => { + return !node.isInline; + }); + } + } + + if (!maybeNode) return; + + activeNode = { node: maybeNode.node!, pos: maybeNode.pos }; + showDragHandleAtDOM(dom as HTMLElement); }, - }), - ]; + keydown: () => { + hideDragHandle(); + }, + mousewheel: () => { + hideDragHandle(); + }, + dragstart: (view) => { + view.dom.classList.add('dragging'); + }, + drop: (view, event) => { + view.dom.classList.remove('dragging'); + hideDragHandle(); + let droppedNode: Node | null = null; + const dropPos = view.posAtCoords({ + left: event.clientX, + top: event.clientY, + }); + + if (!dropPos) return; + + if (view.state.selection instanceof NodeSelection) { + droppedNode = view.state.selection.node; + } + + if (!droppedNode) return; + + const resolvedPos = view.state.doc.resolve(dropPos.pos); + const isDroppedInsideList = resolvedPos.parent.type.name === 'listItem'; + + if ( + view.state.selection instanceof NodeSelection && + view.state.selection.node.type.name === 'listItem' && + !isDroppedInsideList && + listType === 'OL' + ) { + const text = droppedNode.textContent; + if (!text) return; + const paragraph = view.state.schema.nodes.paragraph?.createAndFill({}, view.state.schema.text(text)); + const listItem = view.state.schema.nodes.listItem?.createAndFill({}, paragraph); + const newList = view.state.schema.nodes.orderedList?.createAndFill(null, listItem); + const slice = new Slice(Fragment.from(newList), 0, 0); + view.dragging = { slice, move: event.ctrlKey }; + } + }, + dragend: (view) => { + view.dom.classList.remove('dragging'); + }, + }, + }, + }); +} + +export const Dragable = Extension.create({ + name: 'drag', + + addOptions() { + return { + dragHandleWidth: 20, + scrollTreshold: 100, + dragHandleSelector: 'drag-handle', + }; + }, + + addProseMirrorPlugins() { + return [ + this.editor.isEditable && + DragHandlePlugin({ + editor: this.editor, + pluginKey: 'drag-handle', + dragHandleWidth: this.options.dragHandleWidth, + scrollTreshold: this.options.scrollTreshold, + dragHandleSelector: this.options.dragHandleSelector, + }), + ].filter(Boolean) as Plugin[]; }, }); diff --git a/packages/client/src/tiptap/core/extensions/paste.ts b/packages/client/src/tiptap/core/extensions/paste.ts index 1a12c728..150eb647 100644 --- a/packages/client/src/tiptap/core/extensions/paste.ts +++ b/packages/client/src/tiptap/core/extensions/paste.ts @@ -27,7 +27,7 @@ const reuploadImageAndUpdateSrc = async (img: HTMLImageElement) => { } const blob = await resp.blob(); - const url = await uploadFile?.(blob); + const url = await uploadFile?.(blob as File); img.src = url; return true; @@ -150,87 +150,87 @@ export const Paste = Extension.create({ new Plugin({ key: new PluginKey('paste'), props: { - handlePaste: (view, event: ClipboardEvent) => { - if (view.props.editable && !view.props.editable(view.state)) { - return false; - } + // handlePaste: (view, event: ClipboardEvent) => { + // if (view.props.editable && !view.props.editable(view.state)) { + // return false; + // } - if (!event.clipboardData) return false; + // if (!event.clipboardData) return false; - const files = Array.from(event.clipboardData.files); - const text = event.clipboardData.getData('text/plain'); - const html = event.clipboardData.getData('text/html'); - const vscode = event.clipboardData.getData('vscode-editor-data'); - const node = event.clipboardData.getData('text/node'); - const markdownText = event.clipboardData.getData('text/markdown'); - const { state, dispatch } = view; + // const files = Array.from(event.clipboardData.files); + // const text = event.clipboardData.getData('text/plain'); + // const html = event.clipboardData.getData('text/html'); + // const vscode = event.clipboardData.getData('vscode-editor-data'); + // const node = event.clipboardData.getData('text/node'); + // const markdownText = event.clipboardData.getData('text/markdown'); + // const { state, dispatch } = view; - debug(() => { - console.group('paste'); - console.log({ text, vscode, node, markdownText, files }); - console.log(html); - console.groupEnd(); - }); + // debug(() => { + // console.group('paste'); + // console.log({ text, vscode, node, markdownText, files }); + // console.log(html); + // console.groupEnd(); + // }); - // 直接复制节点 - if (node) { - const json = safeJSONParse(node); - const tr = view.state.tr; - const selection = tr.selection; - view.dispatch(tr.insert(selection.from - 1, view.state.schema.nodeFromJSON(json)).scrollIntoView()); - return true; - } + // // 直接复制节点 + // if (node) { + // const json = safeJSONParse(node); + // const tr = view.state.tr; + // const selection = tr.selection; + // view.dispatch(tr.insert(selection.from - 1, view.state.schema.nodeFromJSON(json)).scrollIntoView()); + // return true; + // } - // 链接 - if (isValidURL(text)) { - if (!state.selection.empty) { - toggleMark(this.editor.schema.marks.link, { href: text })(state, dispatch); - return true; - } + // // 链接 + // if (isValidURL(text)) { + // if (!state.selection.empty) { + // toggleMark(this.editor.schema.marks.link, { href: text })(state, dispatch); + // return true; + // } - const transaction = view.state.tr - .insertText(text, state.selection.from, state.selection.to) - .addMark( - state.selection.from, - state.selection.to + text.length, - state.schema.marks.link.create({ href: text }) - ); - view.dispatch(transaction); - return true; - } + // const transaction = view.state.tr + // .insertText(text, state.selection.from, state.selection.to) + // .addMark( + // state.selection.from, + // state.selection.to + text.length, + // state.schema.marks.link.create({ href: text }) + // ); + // view.dispatch(transaction); + // return true; + // } - // 粘贴代码 - if (isInCode(view.state)) { - event.preventDefault(); - view.dispatch(view.state.tr.insertText(text).scrollIntoView()); - return true; - } + // // 粘贴代码 + // if (isInCode(view.state)) { + // event.preventDefault(); + // view.dispatch(view.state.tr.insertText(text).scrollIntoView()); + // return true; + // } - const vscodeMeta = vscode ? JSON.parse(vscode) : undefined; - const pasteCodeLanguage = vscodeMeta?.mode; + // const vscodeMeta = vscode ? JSON.parse(vscode) : undefined; + // const pasteCodeLanguage = vscodeMeta?.mode; - if (html.length > 0) { - return htmlToProsemirror(editor, html); - } + // if (html.length > 0) { + // return htmlToProsemirror(editor, html); + // } - const { markdownToHTML } = extensionThis.options; + // const { markdownToHTML } = extensionThis.options; - if ((markdownText || isMarkdown(text)) && markdownToHTML) { - event.preventDefault(); - const html = markdownToHTML(normalizeMarkdown(markdownText || text)); - if (html && html.length) return htmlToProsemirror(editor, html, true); - } + // if ((markdownText || isMarkdown(text)) && markdownToHTML) { + // event.preventDefault(); + // const html = markdownToHTML(normalizeMarkdown(markdownText || text)); + // if (html && html.length) return htmlToProsemirror(editor, html, true); + // } - if (files.length) { - event.preventDefault(); - files.forEach((file) => { - handleFileEvent({ editor, file }); - }); - return true; - } + // if (files.length) { + // event.preventDefault(); + // files.forEach((file) => { + // handleFileEvent({ editor, file }); + // }); + // return true; + // } - return false; - }, + // return false; + // }, handleDrop: (view, event: any) => { if (view.props.editable && !view.props.editable(view.state)) { return false; diff --git a/packages/client/src/tiptap/core/styles/drag.scss b/packages/client/src/tiptap/core/styles/drag.scss index 52d0f886..1755f0ae 100644 --- a/packages/client/src/tiptap/core/styles/drag.scss +++ b/packages/client/src/tiptap/core/styles/drag.scss @@ -1,27 +1,31 @@ -[data-drag-handle] { - position: absolute; - z-index: 100; - display: inline; - width: 16px; - height: 16px; - cursor: move; - background-color: #fff; - opacity: 0; - transition: opacity 0.3s ease-out; - background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='16' height='16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='3' y='1' width='3' height='3' rx='1.5' fill='%23111'/%3E%3Crect x='10' y='1' width='3' height='3' rx='1.5' fill='%23111'/%3E%3Crect x='3' y='6' width='3' height='3' rx='1.5' fill='%23111'/%3E%3Crect x='10' y='6' width='3' height='3' rx='1.5' fill='%23111'/%3E%3Crect x='3' y='11' width='3' height='3' rx='1.5' fill='%23111'/%3E%3Crect x='10' y='11' width='3' height='3' rx='1.5' fill='%23111'/%3E%3C/svg%3E"); - background-size: contain; - background-position: center 0; +.drag-handle { + position: fixed; + z-index: 50; + width: 1.4rem; + height: 1.4rem; + cursor: grab; + border: 1px solid var(--yunce-border-color); + border-radius: var(--yunce-border-radius); + opacity: 1; + transition: opacity ease-in 0.2s; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10' style='fill: rgba(0, 0, 0, 0.5)'%3E%3Cpath d='M3,2 C2.44771525,2 2,1.55228475 2,1 C2,0.44771525 2.44771525,0 3,0 C3.55228475,0 4,0.44771525 4,1 C4,1.55228475 3.55228475,2 3,2 Z M3,6 C2.44771525,6 2,5.55228475 2,5 C2,4.44771525 2.44771525,4 3,4 C3.55228475,4 4,4.44771525 4,5 C4,5.55228475 3.55228475,6 3,6 Z M3,10 C2.44771525,10 2,9.55228475 2,9 C2,8.44771525 2.44771525,8 3,8 C3.55228475,8 4,8.44771525 4,9 C4,9.55228475 3.55228475,10 3,10 Z M7,2 C6.44771525,2 6,1.55228475 6,1 C6,0.44771525 6.44771525,0 7,0 C7.55228475,0 8,0.44771525 8,1 C8,1.55228475 7.55228475,2 7,2 Z M7,6 C6.44771525,6 6,5.55228475 6,5 C6,4.44771525 6.44771525,4 7,4 C7.55228475,4 8,4.44771525 8,5 C8,5.55228475 7.55228475,6 7,6 Z M7,10 C6.44771525,10 6,9.55228475 6,9 C6,8.44771525 6.44771525,8 7,8 C7.55228475,8 8,8.44771525 8,9 C8,9.55228475 7.55228475,10 7,10 Z'%3E%3C/path%3E%3C/svg%3E"); + background-size: calc(0.5em + 0.375rem) calc(0.5em + 0.375rem); background-repeat: no-repeat; + background-position: center; - &.show { - opacity: 0.3; + &:hover { + background-color: var(--yunce-hover-bg-color); + transition: background-color 0.2s; + } - &:hover { - opacity: 1; - } + &:active { + cursor: grabbing; + background-color: var(--yunce-hover-bg-color); + transition: background-color 0.2s; } &.hide { + pointer-events: none; opacity: 0; } } diff --git a/packages/client/src/tiptap/core/styles/var.scss b/packages/client/src/tiptap/core/styles/var.scss index 782f007d..ce09cb5e 100644 --- a/packages/client/src/tiptap/core/styles/var.scss +++ b/packages/client/src/tiptap/core/styles/var.scss @@ -10,6 +10,8 @@ /* 自定义节点选中 */ --node-selected-border-color: rgb(0 101 255); + --yunce-border-color: #dee0e3; + --yunce-border-radius: 2px; } body[theme-mode='dark'] {