From 733a4910e96d39ecaa4a34b377632ac5a3ebeb5d Mon Sep 17 00:00:00 2001 From: fantasticit Date: Wed, 27 Apr 2022 22:24:30 +0800 Subject: [PATCH] tiptap: improve resizable --- .../src/components/resizeable/resizeable.tsx | 90 +++++++++++++++---- .../components/resizeable/style.module.scss | 11 +++ .../client/src/tiptap/styles/selection.scss | 1 - .../src/tiptap/wrappers/iframe/index.tsx | 45 ++++------ .../src/tiptap/wrappers/image/index.tsx | 73 ++++++--------- .../client/src/tiptap/wrappers/mind/index.tsx | 25 +++--- 6 files changed, 136 insertions(+), 109 deletions(-) diff --git a/packages/client/src/components/resizeable/resizeable.tsx b/packages/client/src/components/resizeable/resizeable.tsx index bf86afd..85e2aff 100644 --- a/packages/client/src/components/resizeable/resizeable.tsx +++ b/packages/client/src/components/resizeable/resizeable.tsx @@ -10,6 +10,7 @@ interface IProps { width: number; height: number; maxWidth?: number; + isEditable?: boolean; onChange?: (arg: ISize) => void; onChangeEnd?: (arg: ISize) => void; className?: string; @@ -18,26 +19,30 @@ interface IProps { const MIN_WIDTH = 50; const MIN_HEIGHT = 50; -function clamp(val: number, min: number, max: number): number { +function clamp(val: number, min: number, max: number): string { if (val < min) { - return min; + return '' + min; } if (val > max) { - return max; + return '' + max; } - return val; + return '' + val; } export const Resizeable: React.FC = ({ width, height, maxWidth, + isEditable = false, className, onChange, onChangeEnd, children, }) => { const $container = useRef(null); + const $cloneNode = useRef(null); + const $cloneNodeTip = useRef(null); + const $placeholderNode = useRef(null); const $topLeft = useRef(null); const $topRight = useRef(null); const $bottomLeft = useRef(null); @@ -49,6 +54,8 @@ export const Resizeable: React.FC = ({ }); useEffect(() => { + if (!isEditable) return; + interact($container.current).resizable({ edges: { top: true, @@ -58,19 +65,24 @@ export const Resizeable: React.FC = ({ }, listeners: { move: function (event) { - let { x, y } = event.target.dataset; - x = (parseFloat(x) || 0) + event.deltaRect.left; - y = (parseFloat(y) || 0) + event.deltaRect.top; + const placeholderNode = $placeholderNode.current; + Object.assign(placeholderNode.style, { + opacity: 0, + }); + const cloneNode = $cloneNode.current; let { width, height } = event.rect; - width = clamp(width, MIN_WIDTH, maxWidth || Infinity); - height = clamp(height, MIN_HEIGHT, Infinity); - - Object.assign(event.target.style, { + width = parseInt(clamp(width, MIN_WIDTH, maxWidth || Infinity)); + height = parseInt(clamp(height, MIN_HEIGHT, Infinity)); + Object.assign(cloneNode.style, { width: `${width}px`, height: `${height}px`, + zIndex: 1000, }); - Object.assign(event.target.dataset, { x, y }); + + const tipNode = $cloneNodeTip.current; + tipNode.innerText = `${width}x${height}`; + onChange && onChange({ width, height }); }, end: function (event) { @@ -78,11 +90,24 @@ export const Resizeable: React.FC = ({ width = clamp(width, MIN_WIDTH, maxWidth || Infinity); height = clamp(height, MIN_HEIGHT, Infinity); + const cloneNode = $cloneNode.current; + Object.assign(cloneNode.style, { + zIndex: 0, + }); + + const tipNode = $cloneNodeTip.current; + tipNode.innerText = ``; + + const placeholderNode = $placeholderNode.current; + Object.assign(placeholderNode.style, { + opacity: 1, + }); + onChangeEnd && onChangeEnd({ width, height }); }, }, }); - }, [maxWidth]); + }, [maxWidth, isEditable]); useEffect(() => { Object.assign($container.current.style, { @@ -98,10 +123,41 @@ export const Resizeable: React.FC = ({ ref={$container} style={{ width, height }} > - - - - + {isEditable && ( + <> +
+ + + + +
+ +
+ + + + + +
+ + )} + {children} ); diff --git a/packages/client/src/components/resizeable/style.module.scss b/packages/client/src/components/resizeable/style.module.scss index 51cd1e3..6fe9b6d 100644 --- a/packages/client/src/components/resizeable/style.module.scss +++ b/packages/client/src/components/resizeable/style.module.scss @@ -6,6 +6,17 @@ max-width: 100%; box-sizing: border-box; + .cloneNodeWrap { + position: absolute; + background-color: rgb(179 212 255 / 30%); + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + color: #333; + user-select: none; + } + .resizer { position: absolute; z-index: 9999; diff --git a/packages/client/src/tiptap/styles/selection.scss b/packages/client/src/tiptap/styles/selection.scss index 9d59669..fae93af 100644 --- a/packages/client/src/tiptap/styles/selection.scss +++ b/packages/client/src/tiptap/styles/selection.scss @@ -72,7 +72,6 @@ &::after { position: absolute; pointer-events: none; - background-color: rgb(179 212 255 / 30%); border: 1px solid var(--node-selected-border-color) !important; border-radius: var(--border-radius); content: ''; diff --git a/packages/client/src/tiptap/wrappers/iframe/index.tsx b/packages/client/src/tiptap/wrappers/iframe/index.tsx index e35aa8e..7aae71f 100644 --- a/packages/client/src/tiptap/wrappers/iframe/index.tsx +++ b/packages/client/src/tiptap/wrappers/iframe/index.tsx @@ -1,6 +1,6 @@ -import { useCallback, useMemo } from 'react'; +import { useCallback } from 'react'; import cls from 'classnames'; -import { NodeViewWrapper, NodeViewContent } from '@tiptap/react'; +import { NodeViewWrapper } from '@tiptap/react'; import { Typography } from '@douyinfe/semi-ui'; import { Resizeable } from 'components/resizeable'; import { getEditorContainerDOMSize } from '../../utils/editor'; @@ -17,36 +17,21 @@ export const IframeWrapper = ({ editor, node, updateAttributes }) => { updateAttributes({ width: size.width, height: size.height }); }, []); - const content = useMemo( - () => ( - - {url ? ( -
- -
- ) : ( -
- 请设置外链地址 -
- )} -
- ), - [url, width, height] - ); - - if (!isEditable && !url) { - return null; - } - return ( - {isEditable ? ( - -
{content}
-
- ) : ( -
{content}
- )} + +
+ {url ? ( +
+ +
+ ) : ( +
+ 请设置外链地址 +
+ )} +
+
); }; diff --git a/packages/client/src/tiptap/wrappers/image/index.tsx b/packages/client/src/tiptap/wrappers/image/index.tsx index 9d5091a..8f564cc 100644 --- a/packages/client/src/tiptap/wrappers/image/index.tsx +++ b/packages/client/src/tiptap/wrappers/image/index.tsx @@ -1,7 +1,6 @@ -import cls from 'classnames'; -import { useCallback, useEffect, useMemo, useRef } from 'react'; +import { useCallback, useEffect, useRef } from 'react'; import { Typography, Spin } from '@douyinfe/semi-ui'; -import { NodeViewWrapper, NodeViewContent } from '@tiptap/react'; +import { NodeViewWrapper } from '@tiptap/react'; import { LazyLoadImage } from 'react-lazy-load-image-component'; import { Resizeable } from 'components/resizeable'; import { useToggle } from 'hooks/use-toggle'; @@ -58,53 +57,31 @@ export const ImageWrapper = ({ editor, node, updateAttributes }) => { } }, [src, hasTrigger]); - const content = useMemo(() => { - if (error) { - return ( -
- {error} -
- ); - } - - if (!src) { - return ( -
- - {loading ? '正在上传中' : '请选择图片'} - - -
- ); - } - - const img = ; - - if (isEditable) { - return ( - - {img} - - ); - } - - return ( -
- {img} -
- ); - }, [error, src, isEditable, width, height]); - return ( - {content} - + + {error ? ( +
+ {error} +
+ ) : !src ? ( +
+ + {loading ? '正在上传中' : '请选择图片'} + + +
+ ) : ( + + )} +
); }; diff --git a/packages/client/src/tiptap/wrappers/mind/index.tsx b/packages/client/src/tiptap/wrappers/mind/index.tsx index 09b504f..31b896c 100644 --- a/packages/client/src/tiptap/wrappers/mind/index.tsx +++ b/packages/client/src/tiptap/wrappers/mind/index.tsx @@ -1,4 +1,4 @@ -import { NodeViewContent, NodeViewWrapper } from '@tiptap/react'; +import { NodeViewWrapper } from '@tiptap/react'; import cls from 'classnames'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Spin, Typography } from '@douyinfe/semi-ui'; @@ -15,6 +15,8 @@ import styles from './index.module.scss'; const { Text } = Typography; +const INHERIT_SIZE_STYLE = { width: '100%', height: '100%' }; + export const MindWrapper = ({ editor, node, updateAttributes }) => { const $container = useRef(); const $mind = useRef(); @@ -28,14 +30,14 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => { const content = useMemo(() => { if (error) { return ( -
+
{error.message || error}
); } if (loading) { - return ; + return ; } return ( @@ -43,10 +45,10 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => { ref={$container} className={cls(styles.renderWrap, 'render-wrapper')} tabIndex={0} - style={{ width: '100%', height: '100%' }} + style={INHERIT_SIZE_STYLE} >
); - }, [loading, error]); + }, [loading, error, width, height]); const onResize = useCallback( (size) => { @@ -203,15 +205,13 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => { minder.execCommand('theme', theme); }, [theme]); + console.log(width, height); + return ( - {isEditable ? ( - - {content} - - ) : ( -
{content}
- )} + + {content} +
{ setTheme={setTheme} />
-
); };