tiptap: improve drag

pull/123/head
fantasticit 2022-07-14 21:49:39 +08:00
parent 9560066cfa
commit 5f773ec988
14 changed files with 67 additions and 19 deletions

View File

@ -28,6 +28,7 @@ export const Dragable = Extension.create({
name: 'dragable', name: 'dragable',
addProseMirrorPlugins() { addProseMirrorPlugins() {
let scrollContainer;
let dropElement; let dropElement;
let currentNode; let currentNode;
let editorView; let editorView;
@ -54,6 +55,11 @@ export const Dragable = Extension.create({
editorView.dragging = { slice, move: true }; editorView.dragging = { slice, move: true };
} }
function onScroll() {
if (!dropElement) return;
dropElement.style.opacity = 0;
}
return [ return [
new Plugin({ new Plugin({
view(view) { view(view) {
@ -64,6 +70,12 @@ export const Dragable = Extension.create({
dropElement.className = 'drag-handler'; dropElement.className = 'drag-handler';
dropElement.addEventListener('dragstart', drag); dropElement.addEventListener('dragstart', drag);
view.dom.parentElement.appendChild(dropElement); view.dom.parentElement.appendChild(dropElement);
scrollContainer = view.dom.parentElement.parentElement?.parentElement?.parentElement;
if (scrollContainer) {
scrollContainer.addEventListener('scroll', onScroll);
}
} }
return { return {
@ -75,6 +87,10 @@ export const Dragable = Extension.create({
dropElement.removeEventListener('dragstart', drag); dropElement.removeEventListener('dragstart', drag);
dropElement.parentNode.removeChild(dropElement); dropElement.parentNode.removeChild(dropElement);
} }
if (scrollContainer) {
scrollContainer.removeEventListener('scroll', onScroll);
}
}, },
}; };
}, },
@ -91,7 +107,7 @@ export const Dragable = Extension.create({
} }
}, 50); }, 50);
}, },
mousemove(view, event) { mousedown(view, event) {
if (!dropElement) return; if (!dropElement) return;
const coords = { left: event.clientX, top: event.clientY }; const coords = { left: event.clientX, top: event.clientY };
@ -134,10 +150,6 @@ export const Dragable = Extension.create({
dropElement.style.top = rect.top + 6 + 'px'; dropElement.style.top = rect.top + 6 + 'px';
dropElement.style.opacity = 1; dropElement.style.opacity = 1;
}, },
mouseleave() {
if (!dropElement || currentNode) return;
dropElement.style.opacity = 0;
},
}, },
}, },
}), }),

View File

@ -6,6 +6,7 @@ import { Tooltip } from 'components/tooltip';
import { useToggle } from 'hooks/use-toggle'; import { useToggle } from 'hooks/use-toggle';
import { useCallback, useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import { uploadFile } from 'services/file'; import { uploadFile } from 'services/file';
import { Attachment } from 'tiptap/core/extensions/attachment';
import { DragableWrapper } from 'tiptap/core/wrappers/dragable'; import { DragableWrapper } from 'tiptap/core/wrappers/dragable';
import { download, extractFileExtension, extractFilename, normalizeFileSize } from 'tiptap/prose-utils'; import { download, extractFileExtension, extractFilename, normalizeFileSize } from 'tiptap/prose-utils';
@ -154,5 +155,9 @@ export const AttachmentWrapper = ({ editor, node, updateAttributes }) => {
} }
})(); })();
return <DragableWrapper editor={editor}>{content}</DragableWrapper>; return (
<DragableWrapper editor={editor} extensionName={Attachment.name}>
{content}
</DragableWrapper>
);
}; };

View File

@ -4,6 +4,7 @@ import { EmojiPicker } from 'components/emoji-picker';
import { convertColorToRGBA } from 'helpers/color'; import { convertColorToRGBA } from 'helpers/color';
import { Theme, ThemeEnum } from 'hooks/use-theme'; import { Theme, ThemeEnum } from 'hooks/use-theme';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { Callout } from 'tiptap/core/extensions/callout';
import { DragableWrapper } from 'tiptap/core/wrappers/dragable'; import { DragableWrapper } from 'tiptap/core/wrappers/dragable';
import styles from './index.module.scss'; import styles from './index.module.scss';
@ -26,7 +27,12 @@ export const CalloutWrapper = ({ editor, node, updateAttributes }) => {
); );
return ( return (
<DragableWrapper editor={editor} id="js-callout-container" className={cls(styles.wrap)}> <DragableWrapper
editor={editor}
extensionName={Callout.name}
id="js-callout-container"
className={cls(styles.wrap)}
>
<div <div
className={cls(styles.innerWrap, 'render-wrapper')} className={cls(styles.innerWrap, 'render-wrapper')}
style={{ style={{

View File

@ -4,6 +4,7 @@ import { NodeViewContent } from '@tiptap/react';
import cls from 'classnames'; import cls from 'classnames';
import { copy } from 'helpers/copy'; import { copy } from 'helpers/copy';
import React, { useRef } from 'react'; import React, { useRef } from 'react';
import { CodeBlock } from 'tiptap/core/extensions/code-block';
import { DragableWrapper } from 'tiptap/core/wrappers/dragable'; import { DragableWrapper } from 'tiptap/core/wrappers/dragable';
import styles from './index.module.scss'; import styles from './index.module.scss';
@ -14,7 +15,7 @@ export const CodeBlockWrapper = ({ editor, node: { attrs }, updateAttributes, ex
const $container = useRef<HTMLPreElement>(); const $container = useRef<HTMLPreElement>();
return ( return (
<DragableWrapper editor={editor} className={cls(styles.wrap, 'render-wrapper')}> <DragableWrapper editor={editor} extensionName={CodeBlock.name} className={cls(styles.wrap, 'render-wrapper')}>
<div className={styles.handleWrap}> <div className={styles.handleWrap}>
<Select <Select
size="small" size="small"

View File

@ -1,6 +1,7 @@
import { Space, Typography } from '@douyinfe/semi-ui'; import { Space, Typography } from '@douyinfe/semi-ui';
import cls from 'classnames'; import cls from 'classnames';
import Countdown from 'react-countdown'; import ReactCountdown from 'react-countdown';
import { Countdown } from 'tiptap/core/extensions/countdown';
import { DragableWrapper } from 'tiptap/core/wrappers/dragable'; import { DragableWrapper } from 'tiptap/core/wrappers/dragable';
import styles from './index.module.scss'; import styles from './index.module.scss';
@ -32,10 +33,10 @@ export const CountdownWrapper = ({ editor, node }) => {
const { title, date } = node.attrs; const { title, date } = node.attrs;
return ( return (
<DragableWrapper editor={editor}> <DragableWrapper editor={editor} extensionName={Countdown.name}>
<div className={cls(styles.wrap, 'render-wrapper')}> <div className={cls(styles.wrap, 'render-wrapper')}>
<Text>{title}</Text> <Text>{title}</Text>
<Countdown date={date} renderer={renderer}></Countdown> <ReactCountdown date={date} renderer={renderer}></ReactCountdown>
</div> </div>
</DragableWrapper> </DragableWrapper>
); );

View File

@ -7,6 +7,7 @@ import { useChildrenDocument } from 'data/document';
import Link from 'next/link'; import Link from 'next/link';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { DocumentChildren } from 'tiptap/core/extensions/document-children';
import { DragableWrapper } from 'tiptap/core/wrappers/dragable'; import { DragableWrapper } from 'tiptap/core/wrappers/dragable';
import styles from './index.module.scss'; import styles from './index.module.scss';
@ -37,6 +38,7 @@ export const DocumentChildrenWrapper = ({ editor, node, updateAttributes }) => {
return ( return (
<DragableWrapper <DragableWrapper
editor={editor} editor={editor}
extensionName={DocumentChildren.name}
as="div" as="div"
className={cls('render-wrapper', styles.wrap, isEditable && styles.isEditable, 'documentChildren')} className={cls('render-wrapper', styles.wrap, isEditable && styles.isEditable, 'documentChildren')}
> >

View File

@ -3,6 +3,7 @@ import { IconDocument } from 'components/icons';
import Link from 'next/link'; import Link from 'next/link';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { DocumentReference } from 'tiptap/core/extensions/document-reference';
import { DragableWrapper } from 'tiptap/core/wrappers/dragable'; import { DragableWrapper } from 'tiptap/core/wrappers/dragable';
import styles from './index.module.scss'; import styles from './index.module.scss';
@ -50,7 +51,12 @@ export const DocumentReferenceWrapper = ({ editor, node, updateAttributes }) =>
}, [organizationId, wikiId, documentId, isEditable, isShare, title]); }, [organizationId, wikiId, documentId, isEditable, isShare, title]);
return ( return (
<DragableWrapper editor={editor} as="div" className={cls(styles.wrap, isEditable && 'render-wrapper')}> <DragableWrapper
editor={editor}
extensionName={DocumentReference.name}
as="div"
className={cls(styles.wrap, isEditable && 'render-wrapper')}
>
{content} {content}
</DragableWrapper> </DragableWrapper>
); );

View File

@ -21,7 +21,7 @@
} }
&.isEditable { &.isEditable {
&:hover { &.isActive {
.dragHandle { .dragHandle {
opacity: 1; opacity: 1;
} }

View File

@ -2,23 +2,26 @@ import { Editor } from '@tiptap/core';
import { NodeViewWrapper } from '@tiptap/react'; import { NodeViewWrapper } from '@tiptap/react';
import cls from 'classnames'; import cls from 'classnames';
import React, { ElementType } from 'react'; import React, { ElementType } from 'react';
import { useActive } from 'tiptap/core/hooks/use-active';
import styles from './index.module.scss'; import styles from './index.module.scss';
export const DragableWrapper: React.FC<{ export const DragableWrapper: React.FC<{
editor: Editor; editor: Editor;
extensionName: string;
as?: ElementType; as?: ElementType;
id?: string; id?: string;
className?: string; className?: string;
style?: React.CSSProperties; style?: React.CSSProperties;
}> = ({ editor, as = 'div', id, className, style = {}, children }) => { }> = ({ editor, extensionName, as = 'div', id, className, style = {}, children }) => {
const isEditable = editor.isEditable; const isEditable = editor.isEditable;
const isActive = useActive(editor, extensionName);
return ( return (
<NodeViewWrapper <NodeViewWrapper
as={as} as={as}
id={id} id={id}
className={cls(styles.draggableItem, isEditable && styles.isEditable, className)} className={cls(styles.draggableItem, isEditable && styles.isEditable, isActive && styles.isActive, className)}
style={style} style={style}
> >
<div className={styles.dragHandle} contentEditable="false" draggable="true" data-drag-handle /> <div className={styles.dragHandle} contentEditable="false" draggable="true" data-drag-handle />

View File

@ -95,7 +95,11 @@ export const FlowWrapper = ({ editor, node, updateAttributes }) => {
}, [toggleLoading, data]); }, [toggleLoading, data]);
return ( return (
<DragableWrapper editor={editor} className={cls(styles.wrap, isActive && styles.isActive)}> <DragableWrapper
editor={editor}
className={cls(styles.wrap, isActive && styles.isActive)}
extensionName={Flow.name}
>
<VisibilitySensor onChange={onViewportChange}> <VisibilitySensor onChange={onViewportChange}>
<Resizeable isEditable={isEditable} width={width} height={height} maxWidth={maxWidth} onChangeEnd={onResize}> <Resizeable isEditable={isEditable} width={width} height={height} maxWidth={maxWidth} onChangeEnd={onResize}>
<div <div

View File

@ -2,6 +2,7 @@ import { Typography } from '@douyinfe/semi-ui';
import cls from 'classnames'; import cls from 'classnames';
import { Resizeable } from 'components/resizeable'; import { Resizeable } from 'components/resizeable';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { Iframe } from 'tiptap/core/extensions/iframe';
import { DragableWrapper } from 'tiptap/core/wrappers/dragable'; import { DragableWrapper } from 'tiptap/core/wrappers/dragable';
import { getEditorContainerDOMSize } from 'tiptap/prose-utils'; import { getEditorContainerDOMSize } from 'tiptap/prose-utils';
@ -22,7 +23,7 @@ export const IframeWrapper = ({ editor, node, updateAttributes }) => {
); );
return ( return (
<DragableWrapper editor={editor}> <DragableWrapper editor={editor} extensionName={Iframe.name}>
<Resizeable width={width} maxWidth={maxWidth} height={height} isEditable={isEditable} onChangeEnd={onResize}> <Resizeable width={width} maxWidth={maxWidth} height={height} isEditable={isEditable} onChangeEnd={onResize}>
<div className={cls(styles.wrap, 'render-wrapper')}> <div className={cls(styles.wrap, 'render-wrapper')}>
{url ? ( {url ? (

View File

@ -4,6 +4,7 @@ import { useToggle } from 'hooks/use-toggle';
import { useCallback, useEffect, useRef } from 'react'; import { useCallback, useEffect, useRef } from 'react';
import { LazyLoadImage } from 'react-lazy-load-image-component'; import { LazyLoadImage } from 'react-lazy-load-image-component';
import { uploadFile } from 'services/file'; import { uploadFile } from 'services/file';
import { Image } from 'tiptap/core/extensions/image';
import { DragableWrapper } from 'tiptap/core/wrappers/dragable'; import { DragableWrapper } from 'tiptap/core/wrappers/dragable';
import { import {
extractFileExtension, extractFileExtension,
@ -69,7 +70,7 @@ export const ImageWrapper = ({ editor, node, updateAttributes }) => {
}, [src, hasTrigger, selectFile, updateAttributes]); }, [src, hasTrigger, selectFile, updateAttributes]);
return ( return (
<DragableWrapper editor={editor} style={{ textAlign, fontSize: 0, maxWidth: '100%' }}> <DragableWrapper editor={editor} extensionName={Image.name} style={{ textAlign, fontSize: 0, maxWidth: '100%' }}>
<Resizeable <Resizeable
className={'render-wrapper'} className={'render-wrapper'}
width={width || maxWidth} width={width || maxWidth}

View File

@ -2,6 +2,7 @@ import { convertColorToRGBA } from 'helpers/color';
import { Theme, ThemeEnum } from 'hooks/use-theme'; import { Theme, ThemeEnum } from 'hooks/use-theme';
import katex from 'katex'; import katex from 'katex';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { Katex } from 'tiptap/core/extensions/katex';
import { DragableWrapper } from 'tiptap/core/wrappers/dragable'; import { DragableWrapper } from 'tiptap/core/wrappers/dragable';
import styles from './index.module.scss'; import styles from './index.module.scss';
@ -36,6 +37,7 @@ export const KatexWrapper = ({ node, editor }) => {
return ( return (
<DragableWrapper <DragableWrapper
editor={editor} editor={editor}
extensionName={Katex.name}
className={'render-wrapper'} className={'render-wrapper'}
style={{ style={{
backgroundColor, backgroundColor,

View File

@ -108,7 +108,11 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => {
}, [width, height, setCenter]); }, [width, height, setCenter]);
return ( return (
<DragableWrapper editor={editor} className={cls(styles.wrap, isActive && styles.isActive)}> <DragableWrapper
editor={editor}
extensionName={Mind.name}
className={cls(styles.wrap, isActive && styles.isActive)}
>
<VisibilitySensor onChange={onViewportChange}> <VisibilitySensor onChange={onViewportChange}>
<Resizeable isEditable={isEditable} width={width} height={height} maxWidth={maxWidth} onChangeEnd={onResize}> <Resizeable isEditable={isEditable} width={width} height={height} maxWidth={maxWidth} onChangeEnd={onResize}>
<div <div