mirror of https://github.com/fantasticit/think.git
Merge pull request #144 from fantasticit/fix/drag
This commit is contained in:
commit
a4ea4bf965
|
@ -158,6 +158,8 @@ export interface CodeBlockLowlightOptions extends CodeBlockOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CodeBlock = BuiltInCodeBlock.extend<CodeBlockLowlightOptions>({
|
export const CodeBlock = BuiltInCodeBlock.extend<CodeBlockLowlightOptions>({
|
||||||
|
draggable: true,
|
||||||
|
|
||||||
addOptions() {
|
addOptions() {
|
||||||
return {
|
return {
|
||||||
...this.parent?.(),
|
...this.parent?.(),
|
||||||
|
|
|
@ -64,15 +64,12 @@ export const Countdown = Node.create({
|
||||||
}
|
}
|
||||||
|
|
||||||
const { selection } = editor.state;
|
const { selection } = editor.state;
|
||||||
const pos = selection.$head;
|
|
||||||
|
|
||||||
return chain()
|
return chain()
|
||||||
.insertContentAt(pos.after(), [
|
.insertContent({
|
||||||
{
|
type: this.name,
|
||||||
type: this.name,
|
attrs: options,
|
||||||
attrs: options,
|
})
|
||||||
},
|
|
||||||
])
|
|
||||||
.run();
|
.run();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,158 +0,0 @@
|
||||||
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 scrollContainer;
|
|
||||||
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 };
|
|
||||||
}
|
|
||||||
|
|
||||||
function onScroll() {
|
|
||||||
if (!dropElement) return;
|
|
||||||
dropElement.style.opacity = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
new Plugin({
|
|
||||||
view(view) {
|
|
||||||
if (view.editable) {
|
|
||||||
editorView = view;
|
|
||||||
dropElement = document.createElement('div');
|
|
||||||
dropElement.setAttribute('draggable', 'true');
|
|
||||||
dropElement.className = 'drag-handler';
|
|
||||||
dropElement.addEventListener('dragstart', drag);
|
|
||||||
view.dom.parentElement.appendChild(dropElement);
|
|
||||||
|
|
||||||
scrollContainer = view.dom.parentElement.parentElement?.parentElement?.parentElement;
|
|
||||||
|
|
||||||
if (scrollContainer) {
|
|
||||||
scrollContainer.addEventListener('scroll', onScroll);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
update(view) {
|
|
||||||
editorView = view;
|
|
||||||
},
|
|
||||||
destroy() {
|
|
||||||
if (dropElement && dropElement.parentNode) {
|
|
||||||
dropElement.removeEventListener('dragstart', drag);
|
|
||||||
dropElement.parentNode.removeChild(dropElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scrollContainer) {
|
|
||||||
scrollContainer.removeEventListener('scroll', onScroll);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
handleDOMEvents: {
|
|
||||||
drop() {
|
|
||||||
if (!dropElement) return;
|
|
||||||
|
|
||||||
dropElement.style.opacity = 0;
|
|
||||||
setTimeout(() => {
|
|
||||||
const node = document.querySelector('.ProseMirror-hideselection');
|
|
||||||
if (node) {
|
|
||||||
node.classList.remove('ProseMirror-hideselection');
|
|
||||||
}
|
|
||||||
}, 50);
|
|
||||||
},
|
|
||||||
mousedown(view, event) {
|
|
||||||
if (!dropElement) return;
|
|
||||||
|
|
||||||
const coords = { left: event.clientX, top: event.clientY };
|
|
||||||
const pos = view.posAtCoords(coords);
|
|
||||||
|
|
||||||
if (!pos) {
|
|
||||||
dropElement.style.opacity = 0;
|
|
||||||
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) {
|
|
||||||
dropElement.style.opacity = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node?.classList?.contains('node-title') || node?.classList?.contains('node-table')) {
|
|
||||||
dropElement.style.opacity = 0;
|
|
||||||
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;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -84,15 +84,12 @@ export const Flow = Node.create({
|
||||||
}
|
}
|
||||||
|
|
||||||
const { selection } = editor.state;
|
const { selection } = editor.state;
|
||||||
const pos = selection.$head;
|
|
||||||
|
|
||||||
return chain()
|
return chain()
|
||||||
.insertContentAt(pos.after(), [
|
.insertContent({
|
||||||
{
|
type: this.name,
|
||||||
type: this.name,
|
attrs: options,
|
||||||
attrs: options,
|
})
|
||||||
},
|
|
||||||
])
|
|
||||||
.run();
|
.run();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -84,15 +84,12 @@ export const Iframe = Node.create({
|
||||||
|
|
||||||
const attrs = options || { url: '' };
|
const attrs = options || { url: '' };
|
||||||
const { selection } = editor.state;
|
const { selection } = editor.state;
|
||||||
const pos = selection.$head;
|
|
||||||
|
|
||||||
return chain()
|
return chain()
|
||||||
.insertContentAt(pos.after(), [
|
.insertContent({
|
||||||
{
|
type: this.name,
|
||||||
type: this.name,
|
attrs,
|
||||||
attrs,
|
})
|
||||||
},
|
|
||||||
])
|
|
||||||
.run();
|
.run();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -95,15 +95,12 @@ export const Mind = Node.create({
|
||||||
}
|
}
|
||||||
|
|
||||||
const { selection } = editor.state;
|
const { selection } = editor.state;
|
||||||
const pos = selection.$head;
|
|
||||||
|
|
||||||
return chain()
|
return chain()
|
||||||
.insertContentAt(pos.after(), [
|
.insertContent({
|
||||||
{
|
type: this.name,
|
||||||
type: this.name,
|
attrs: options,
|
||||||
attrs: options,
|
})
|
||||||
},
|
|
||||||
])
|
|
||||||
.run();
|
.run();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,22 @@
|
||||||
|
import { mergeAttributes } from '@tiptap/core';
|
||||||
import TitapParagraph from '@tiptap/extension-paragraph';
|
import TitapParagraph from '@tiptap/extension-paragraph';
|
||||||
|
|
||||||
export const Paragraph = TitapParagraph.extend({});
|
export const Paragraph = TitapParagraph.extend({
|
||||||
|
draggable: true,
|
||||||
|
|
||||||
|
renderHTML({ HTMLAttributes }) {
|
||||||
|
return [
|
||||||
|
'p',
|
||||||
|
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
||||||
|
[
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
'contentEditable': 'false',
|
||||||
|
'draggable': 'true',
|
||||||
|
'data-drag-handle': 'true',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['div', 0],
|
||||||
|
];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
|
@ -22,6 +22,16 @@ export const useEditor = (options: Partial<EditorOptions> = {}, deps: Dependency
|
||||||
const forceUpdate = useForceUpdate();
|
const forceUpdate = useForceUpdate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
options.editorProps = options.editorProps || {};
|
||||||
|
|
||||||
|
if (options.editable) {
|
||||||
|
options.editorProps.attributes = options.editorProps.attributes || {};
|
||||||
|
// @ts-ignore
|
||||||
|
options.editorProps.attributes.class = options.editorProps.attributes.class || '';
|
||||||
|
// @ts-ignore
|
||||||
|
options.editorProps.attributes.class += ' is-editable';
|
||||||
|
}
|
||||||
|
|
||||||
const instance = new Editor(options);
|
const instance = new Editor(options);
|
||||||
|
|
||||||
setEditor(instance);
|
setEditor(instance);
|
||||||
|
|
|
@ -1,11 +1,91 @@
|
||||||
.drag-handler {
|
/* stylelint-disable */
|
||||||
position: fixed;
|
.ProseMirror {
|
||||||
width: 16px;
|
&.is-editable {
|
||||||
height: 16px;
|
[data-drag-handle] {
|
||||||
cursor: grab;
|
position: relative;
|
||||||
opacity: 1;
|
display: inline;
|
||||||
background-image: url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 16"><path fill-opacity="0.2" d="M4 14c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zM2 6C.9 6 0 6.9 0 8s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6C.9 0 0 .9 0 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" /></svg>');
|
opacity: 0;
|
||||||
background-repeat: no-repeat;
|
transition: opacity 0.3s ease-out;
|
||||||
background-size: contain;
|
z-index: 100;
|
||||||
background-position: center;
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: -24px;
|
||||||
|
top: 2px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
text-align: center;
|
||||||
|
margin-left: auto;
|
||||||
|
cursor: move;
|
||||||
|
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;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
filter: var(--invert-filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
[data-drag-handle] {
|
||||||
|
&::before {
|
||||||
|
top: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
[data-drag-handle] {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
li {
|
||||||
|
[data-drag-handle] {
|
||||||
|
&::before {
|
||||||
|
left: -36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-type='taskList'] {
|
||||||
|
li {
|
||||||
|
[data-drag-handle] {
|
||||||
|
&::before {
|
||||||
|
left: -46px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ol {
|
||||||
|
li {
|
||||||
|
[data-drag-handle] {
|
||||||
|
&::before {
|
||||||
|
left: -36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-container {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
[data-drag-handle] {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-content {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-codeBlock,
|
.node-codeBlock,
|
||||||
.node-katex {
|
.node-katex,
|
||||||
|
.node-documentChildren {
|
||||||
.render-wrapper {
|
.render-wrapper {
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
import { IconClose, IconDownload, IconPlayCircle } from '@douyinfe/semi-icons';
|
import { IconClose, IconDownload, IconPlayCircle } from '@douyinfe/semi-icons';
|
||||||
import { Button, Collapsible, Progress, Space, Spin, Toast, Typography } from '@douyinfe/semi-ui';
|
import { Button, Collapsible, Progress, Space, Spin, Toast, Typography } from '@douyinfe/semi-ui';
|
||||||
import { FILE_CHUNK_SIZE } from '@think/domains';
|
import { FILE_CHUNK_SIZE } from '@think/domains';
|
||||||
|
import { NodeViewWrapper } from '@tiptap/react';
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import { Tooltip } from 'components/tooltip';
|
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 { download, extractFileExtension, extractFilename, normalizeFileSize } from 'tiptap/prose-utils';
|
import { download, extractFileExtension, extractFilename, normalizeFileSize } from 'tiptap/prose-utils';
|
||||||
|
|
||||||
import { getFileTypeIcon } from './file-icon';
|
import { getFileTypeIcon } from './file-icon';
|
||||||
|
@ -156,8 +155,9 @@ export const AttachmentWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
})();
|
})();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DragableWrapper editor={editor} extensionName={Attachment.name}>
|
<NodeViewWrapper className={'drag-container'}>
|
||||||
{content}
|
<div className={'drag-handle'} contentEditable="false" draggable="true" data-drag-handle />
|
||||||
</DragableWrapper>
|
<div className={'drag-content'}>{content}</div>
|
||||||
|
</NodeViewWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -44,4 +44,10 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-node-view-content] {
|
||||||
|
[data-drag-handle] {
|
||||||
|
opacity: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
import { NodeViewContent } from '@tiptap/react';
|
import { NodeViewContent, NodeViewWrapper } from '@tiptap/react';
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import { EmojiPicker } from 'components/emoji-picker';
|
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 styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
|
@ -27,32 +25,30 @@ export const CalloutWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DragableWrapper
|
<NodeViewWrapper id="js-callout-container" className={cls('drag-container', styles.wrap)}>
|
||||||
editor={editor}
|
<div className={'drag-handle'} contentEditable="false" draggable="true" data-drag-handle />
|
||||||
extensionName={Callout.name}
|
<div className={'drag-content'}>
|
||||||
id="js-callout-container"
|
<div
|
||||||
className={cls(styles.wrap)}
|
className={cls(styles.innerWrap, 'render-wrapper')}
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={cls(styles.innerWrap, 'render-wrapper')}
|
|
||||||
style={{
|
|
||||||
borderColor,
|
|
||||||
backgroundColor: backgroundColorOpacity,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isEditable ? (
|
|
||||||
<EmojiPicker onSelectEmoji={onSelectEmoji}>
|
|
||||||
<span className={styles.icon}>{emoji || 'Icon'}</span>
|
|
||||||
</EmojiPicker>
|
|
||||||
) : (
|
|
||||||
emoji && <span className={styles.icon}>{emoji}</span>
|
|
||||||
)}
|
|
||||||
<NodeViewContent
|
|
||||||
style={{
|
style={{
|
||||||
color: textColor,
|
borderColor,
|
||||||
|
backgroundColor: backgroundColorOpacity,
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
{isEditable ? (
|
||||||
|
<EmojiPicker onSelectEmoji={onSelectEmoji}>
|
||||||
|
<span className={styles.icon}>{emoji || 'Icon'}</span>
|
||||||
|
</EmojiPicker>
|
||||||
|
) : (
|
||||||
|
emoji && <span className={styles.icon}>{emoji}</span>
|
||||||
|
)}
|
||||||
|
<NodeViewContent
|
||||||
|
style={{
|
||||||
|
color: textColor,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DragableWrapper>
|
</NodeViewWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { IconCopy } from '@douyinfe/semi-icons';
|
import { IconCopy } from '@douyinfe/semi-icons';
|
||||||
import { Button, Select, Tooltip } from '@douyinfe/semi-ui';
|
import { Button, Select, Tooltip } from '@douyinfe/semi-ui';
|
||||||
import { NodeViewContent } from '@tiptap/react';
|
import { NodeViewContent, NodeViewWrapper } 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';
|
||||||
|
@ -16,40 +16,39 @@ export const CodeBlockWrapper = ({ editor, node: { attrs }, updateAttributes, ex
|
||||||
const $container = useRef<HTMLPreElement>();
|
const $container = useRef<HTMLPreElement>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DragableWrapper
|
<NodeViewWrapper className={cls('drag-container', styles.wrap, !isPrint && styles.maxHeight, 'render-wrapper')}>
|
||||||
editor={editor}
|
<div className={'drag-handle'} contentEditable="false" draggable="true" data-drag-handle />
|
||||||
extensionName={CodeBlock.name}
|
<div className={'drag-content'}>
|
||||||
className={cls(styles.wrap, !isPrint && styles.maxHeight, 'render-wrapper')}
|
<div className={styles.handleWrap}>
|
||||||
>
|
<Select
|
||||||
<div className={styles.handleWrap}>
|
|
||||||
<Select
|
|
||||||
size="small"
|
|
||||||
defaultValue={defaultLanguage || 'null'}
|
|
||||||
onChange={(value) => updateAttributes({ language: value })}
|
|
||||||
className={styles.selectorWrap}
|
|
||||||
disabled={!isEditable}
|
|
||||||
filter
|
|
||||||
>
|
|
||||||
<Select.Option value="null">auto</Select.Option>
|
|
||||||
{extension.options.lowlight.listLanguages().map((lang, index) => (
|
|
||||||
<Select.Option key={index} value={lang}>
|
|
||||||
{lang}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
<Tooltip content="复制" spacing={6}>
|
|
||||||
<Button
|
|
||||||
size="small"
|
size="small"
|
||||||
type="tertiary"
|
defaultValue={defaultLanguage || 'null'}
|
||||||
theme="borderless"
|
onChange={(value) => updateAttributes({ language: value })}
|
||||||
icon={<IconCopy />}
|
className={styles.selectorWrap}
|
||||||
onClick={() => copy($container.current.innerText)}
|
disabled={!isEditable}
|
||||||
/>
|
filter
|
||||||
</Tooltip>
|
>
|
||||||
|
<Select.Option value="null">auto</Select.Option>
|
||||||
|
{extension.options.lowlight.listLanguages().map((lang, index) => (
|
||||||
|
<Select.Option key={index} value={lang}>
|
||||||
|
{lang}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
<Tooltip content="复制" spacing={6}>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
type="tertiary"
|
||||||
|
theme="borderless"
|
||||||
|
icon={<IconCopy />}
|
||||||
|
onClick={() => copy($container.current.innerText)}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<pre ref={$container}>
|
||||||
|
<NodeViewContent as="code" />
|
||||||
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
<pre ref={$container}>
|
</NodeViewWrapper>
|
||||||
<NodeViewContent as="code" />
|
|
||||||
</pre>
|
|
||||||
</DragableWrapper>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { Space, Typography } from '@douyinfe/semi-ui';
|
import { Space, Typography } from '@douyinfe/semi-ui';
|
||||||
|
import { NodeViewWrapper } from '@tiptap/react';
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import ReactCountdown from 'react-countdown';
|
import ReactCountdown from 'react-countdown';
|
||||||
import { Countdown } from 'tiptap/core/extensions/countdown';
|
|
||||||
import { DragableWrapper } from 'tiptap/core/wrappers/dragable';
|
|
||||||
|
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
|
@ -33,11 +32,14 @@ export const CountdownWrapper = ({ editor, node }) => {
|
||||||
const { title, date } = node.attrs;
|
const { title, date } = node.attrs;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DragableWrapper editor={editor} extensionName={Countdown.name}>
|
<NodeViewWrapper className={'drag-container'}>
|
||||||
<div className={cls(styles.wrap, 'render-wrapper')}>
|
<div className={'drag-handle'} contentEditable="false" draggable="true" data-drag-handle />
|
||||||
<Text>{title}</Text>
|
<div className={'drag-content'}>
|
||||||
<ReactCountdown date={date} renderer={renderer}></ReactCountdown>
|
<div className={cls(styles.wrap, 'render-wrapper')}>
|
||||||
|
<Text>{title}</Text>
|
||||||
|
<ReactCountdown date={date} renderer={renderer}></ReactCountdown>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DragableWrapper>
|
</NodeViewWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
.wrap {
|
.wrap {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
border: 1px solid var(--node-border-color);
|
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Typography } from '@douyinfe/semi-ui';
|
import { Typography } from '@douyinfe/semi-ui';
|
||||||
|
import { NodeViewWrapper } from '@tiptap/react';
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import { DataRender } from 'components/data-render';
|
import { DataRender } from 'components/data-render';
|
||||||
import { Empty } from 'components/empty';
|
import { Empty } from 'components/empty';
|
||||||
|
@ -7,8 +8,6 @@ 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 styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
|
@ -36,13 +35,9 @@ export const DocumentChildrenWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
}, [node.attrs, wikiId, documentId, updateAttributes]);
|
}, [node.attrs, wikiId, documentId, updateAttributes]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DragableWrapper
|
<NodeViewWrapper as="div" className={cls('drag-container', 'render-wrapper')}>
|
||||||
editor={editor}
|
<div className={'drag-handle'} contentEditable="false" draggable="true" data-drag-handle />
|
||||||
extensionName={DocumentChildren.name}
|
<div className={cls('drag-content', styles.wrap, isEditable && styles.isEditable, 'documentChildren')}>
|
||||||
as="div"
|
|
||||||
className={cls('render-wrapper', styles.wrap, isEditable && styles.isEditable, 'documentChildren')}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<div>
|
<div>
|
||||||
<Text type="tertiary">子文档</Text>
|
<Text type="tertiary">子文档</Text>
|
||||||
</div>
|
</div>
|
||||||
|
@ -82,6 +77,6 @@ export const DocumentChildrenWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
<Text type="tertiary">当前页面无法使用子文档</Text>
|
<Text type="tertiary">当前页面无法使用子文档</Text>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</DragableWrapper>
|
</NodeViewWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
|
import { NodeViewWrapper } from '@tiptap/react';
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import { IconDocument } from 'components/icons';
|
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 styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
|
@ -51,13 +50,9 @@ export const DocumentReferenceWrapper = ({ editor, node, updateAttributes }) =>
|
||||||
}, [organizationId, wikiId, documentId, isEditable, isShare, title]);
|
}, [organizationId, wikiId, documentId, isEditable, isShare, title]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DragableWrapper
|
<NodeViewWrapper as="div" className={cls('drag-container', styles.wrap, isEditable && 'render-wrapper')}>
|
||||||
editor={editor}
|
<div className={'drag-handle'} contentEditable="false" draggable="true" data-drag-handle />
|
||||||
extensionName={DocumentReference.name}
|
<div className={'drag-content'}>{content}</div>
|
||||||
as="div"
|
</NodeViewWrapper>
|
||||||
className={cls(styles.wrap, isEditable && 'render-wrapper')}
|
|
||||||
>
|
|
||||||
{content}
|
|
||||||
</DragableWrapper>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
.draggableItem {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
.dragHandle {
|
|
||||||
position: absolute;
|
|
||||||
top: 0.3rem;
|
|
||||||
left: -24px;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
cursor: grab;
|
|
||||||
opacity: 0;
|
|
||||||
background-image: url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 16"><path fill-opacity="0.2" d="M4 14c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zM2 6C.9 6 0 6.9 0 8s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6C.9 0 0 .9 0 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" /></svg>');
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: contain;
|
|
||||||
background-position: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.isEditable {
|
|
||||||
&.isActive {
|
|
||||||
.dragHandle {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
import { Editor } from '@tiptap/core';
|
|
||||||
import { NodeViewWrapper } from '@tiptap/react';
|
|
||||||
import cls from 'classnames';
|
|
||||||
import React, { ElementType } from 'react';
|
|
||||||
import { useActive } from 'tiptap/core/hooks/use-active';
|
|
||||||
|
|
||||||
import styles from './index.module.scss';
|
|
||||||
|
|
||||||
export const DragableWrapper: React.FC<{
|
|
||||||
editor: Editor;
|
|
||||||
extensionName: string;
|
|
||||||
as?: ElementType;
|
|
||||||
id?: string;
|
|
||||||
className?: string;
|
|
||||||
style?: React.CSSProperties;
|
|
||||||
}> = ({ editor, extensionName, as = 'div', id, className, style = {}, children }) => {
|
|
||||||
const isEditable = editor.isEditable;
|
|
||||||
const isActive = useActive(editor, extensionName);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<NodeViewWrapper
|
|
||||||
as={as}
|
|
||||||
id={id}
|
|
||||||
className={cls(styles.draggableItem, isEditable && styles.isEditable, isActive && styles.isActive, className)}
|
|
||||||
style={style}
|
|
||||||
>
|
|
||||||
<div className={styles.dragHandle} contentEditable="false" draggable="true" data-drag-handle />
|
|
||||||
<div className={styles.content}>{children}</div>
|
|
||||||
</NodeViewWrapper>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Button, Space, Spin, Typography } from '@douyinfe/semi-ui';
|
import { Button, Space, Spin, Typography } from '@douyinfe/semi-ui';
|
||||||
|
import { NodeViewWrapper } from '@tiptap/react';
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import { IconFlow, IconMindCenter, IconZoomIn, IconZoomOut } from 'components/icons';
|
import { IconFlow, IconMindCenter, IconZoomIn, IconZoomOut } from 'components/icons';
|
||||||
import { Resizeable } from 'components/resizeable';
|
import { Resizeable } from 'components/resizeable';
|
||||||
|
@ -7,7 +8,6 @@ import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import VisibilitySensor from 'react-visibility-sensor';
|
import VisibilitySensor from 'react-visibility-sensor';
|
||||||
import { load, renderXml } from 'thirtypart/diagram';
|
import { load, renderXml } from 'thirtypart/diagram';
|
||||||
import { Flow } from 'tiptap/core/extensions/flow';
|
import { Flow } from 'tiptap/core/extensions/flow';
|
||||||
import { DragableWrapper } from 'tiptap/core/wrappers/dragable';
|
|
||||||
import { getEditorContainerDOMSize } from 'tiptap/prose-utils';
|
import { getEditorContainerDOMSize } from 'tiptap/prose-utils';
|
||||||
|
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
@ -95,49 +95,48 @@ export const FlowWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
}, [toggleLoading, data]);
|
}, [toggleLoading, data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DragableWrapper
|
<NodeViewWrapper className={cls('drag-container', isActive && styles.isActive)}>
|
||||||
editor={editor}
|
<div className={'drag-handle'} contentEditable="false" draggable="true" data-drag-handle />
|
||||||
className={cls(styles.wrap, isActive && styles.isActive)}
|
<div className={cls('drag-content', styles.wrap)}>
|
||||||
extensionName={Flow.name}
|
<VisibilitySensor onChange={onViewportChange}>
|
||||||
>
|
<Resizeable isEditable={isEditable} width={width} height={height} maxWidth={maxWidth} onChangeEnd={onResize}>
|
||||||
<VisibilitySensor onChange={onViewportChange}>
|
<div
|
||||||
<Resizeable isEditable={isEditable} width={width} height={height} maxWidth={maxWidth} onChangeEnd={onResize}>
|
className={cls(styles.renderWrap, 'render-wrapper')}
|
||||||
<div
|
style={{ ...INHERIT_SIZE_STYLE, overflow: 'hidden', backgroundColor: bgColor }}
|
||||||
className={cls(styles.renderWrap, 'render-wrapper')}
|
>
|
||||||
style={{ ...INHERIT_SIZE_STYLE, overflow: 'hidden', backgroundColor: bgColor }}
|
{loading && (
|
||||||
>
|
<div>
|
||||||
{loading && (
|
<Spin spinning>
|
||||||
<div>
|
{/* FIXME: semi-design 的问题,不加 div,文字会换行! */}
|
||||||
<Spin spinning>
|
<div></div>
|
||||||
{/* FIXME: semi-design 的问题,不加 div,文字会换行! */}
|
</Spin>
|
||||||
<div></div>
|
</div>
|
||||||
</Spin>
|
)}
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{error && <Text>{(error && error.message) || '未知错误'}</Text>}
|
{error && <Text>{(error && error.message) || '未知错误'}</Text>}
|
||||||
|
|
||||||
{!loading && !error && visible && <div style={{ maxHeight: '100%' }} ref={setMxgraph}></div>}
|
{!loading && !error && visible && <div style={{ maxHeight: '100%' }} ref={setMxgraph}></div>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.title}>
|
<div className={styles.title}>
|
||||||
<Space>
|
<Space>
|
||||||
<span className={styles.icon}>
|
<span className={styles.icon}>
|
||||||
<IconFlow />
|
<IconFlow />
|
||||||
</span>
|
</span>
|
||||||
流程图
|
流程图
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.toolbarWrap}>
|
<div className={styles.toolbarWrap}>
|
||||||
<Space spacing={2}>
|
<Space spacing={2}>
|
||||||
<Button type="tertiary" theme="borderless" size="small" onClick={center} icon={<IconMindCenter />} />
|
<Button type="tertiary" theme="borderless" size="small" onClick={center} icon={<IconMindCenter />} />
|
||||||
<Button type="tertiary" theme="borderless" size="small" onClick={zoomOut} icon={<IconZoomOut />} />
|
<Button type="tertiary" theme="borderless" size="small" onClick={zoomOut} icon={<IconZoomOut />} />
|
||||||
<Button type="tertiary" theme="borderless" size="small" onClick={zoomIn} icon={<IconZoomIn />} />
|
<Button type="tertiary" theme="borderless" size="small" onClick={zoomIn} icon={<IconZoomIn />} />
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
</Resizeable>
|
</Resizeable>
|
||||||
</VisibilitySensor>
|
</VisibilitySensor>
|
||||||
</DragableWrapper>
|
</div>
|
||||||
|
</NodeViewWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { Typography } from '@douyinfe/semi-ui';
|
import { Typography } from '@douyinfe/semi-ui';
|
||||||
|
import { NodeViewWrapper } from '@tiptap/react';
|
||||||
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 { getEditorContainerDOMSize } from 'tiptap/prose-utils';
|
import { getEditorContainerDOMSize } from 'tiptap/prose-utils';
|
||||||
|
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
@ -23,20 +22,23 @@ export const IframeWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DragableWrapper editor={editor} extensionName={Iframe.name}>
|
<NodeViewWrapper className={'drag-container'}>
|
||||||
<Resizeable width={width} maxWidth={maxWidth} height={height} isEditable={isEditable} onChangeEnd={onResize}>
|
<div className={'drag-handle'} contentEditable="false" draggable="true" data-drag-handle />
|
||||||
<div className={cls(styles.wrap, 'render-wrapper')}>
|
<div className={'drag-content'}>
|
||||||
{url ? (
|
<Resizeable width={width} maxWidth={maxWidth} height={height} isEditable={isEditable} onChangeEnd={onResize}>
|
||||||
<div className={styles.innerWrap} style={{ pointerEvents: !isEditable ? 'auto' : 'none' }}>
|
<div className={cls(styles.wrap, 'render-wrapper')}>
|
||||||
<iframe src={url}></iframe>
|
{url ? (
|
||||||
</div>
|
<div className={styles.innerWrap} style={{ pointerEvents: !isEditable ? 'auto' : 'none' }}>
|
||||||
) : (
|
<iframe src={url}></iframe>
|
||||||
<div className={styles.emptyWrap}>
|
</div>
|
||||||
<Text>请设置外链地址</Text>
|
) : (
|
||||||
</div>
|
<div className={styles.emptyWrap}>
|
||||||
)}
|
<Text>请设置外链地址</Text>
|
||||||
</div>
|
</div>
|
||||||
</Resizeable>
|
)}
|
||||||
</DragableWrapper>
|
</div>
|
||||||
|
</Resizeable>
|
||||||
|
</div>
|
||||||
|
</NodeViewWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import { Spin, Typography } from '@douyinfe/semi-ui';
|
import { Spin, Typography } from '@douyinfe/semi-ui';
|
||||||
|
import { NodeViewWrapper } from '@tiptap/react';
|
||||||
import { Resizeable } from 'components/resizeable';
|
import { Resizeable } from 'components/resizeable';
|
||||||
import { useToggle } from 'hooks/use-toggle';
|
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 {
|
import {
|
||||||
extractFileExtension,
|
extractFileExtension,
|
||||||
extractFilename,
|
extractFilename,
|
||||||
|
@ -70,30 +69,33 @@ export const ImageWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
}, [src, hasTrigger, selectFile, updateAttributes]);
|
}, [src, hasTrigger, selectFile, updateAttributes]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DragableWrapper editor={editor} extensionName={Image.name} style={{ textAlign, fontSize: 0, maxWidth: '100%' }}>
|
<NodeViewWrapper className={'drag-container'} style={{ textAlign, fontSize: 0, maxWidth: '100%' }}>
|
||||||
<Resizeable
|
<div className={'drag-handle'} contentEditable="false" draggable="true" data-drag-handle />
|
||||||
className={'render-wrapper'}
|
<div className={'drag-content'}>
|
||||||
width={width || maxWidth}
|
<Resizeable
|
||||||
height={height}
|
className={'render-wrapper'}
|
||||||
maxWidth={maxWidth}
|
width={width || maxWidth}
|
||||||
isEditable={isEditable}
|
height={height}
|
||||||
onChangeEnd={onResize}
|
maxWidth={maxWidth}
|
||||||
>
|
isEditable={isEditable}
|
||||||
{error ? (
|
onChangeEnd={onResize}
|
||||||
<div className={styles.wrap}>
|
>
|
||||||
<Text>{error}</Text>
|
{error ? (
|
||||||
</div>
|
<div className={styles.wrap}>
|
||||||
) : !src ? (
|
<Text>{error}</Text>
|
||||||
<div className={styles.wrap} onClick={selectFile}>
|
</div>
|
||||||
<Spin spinning={loading}>
|
) : !src ? (
|
||||||
<Text style={{ cursor: 'pointer' }}>{loading ? '正在上传中' : '请选择图片'}</Text>
|
<div className={styles.wrap} onClick={selectFile}>
|
||||||
<input ref={$upload} accept="image/*" type="file" hidden onChange={handleFile} />
|
<Spin spinning={loading}>
|
||||||
</Spin>
|
<Text style={{ cursor: 'pointer' }}>{loading ? '正在上传中' : '请选择图片'}</Text>
|
||||||
</div>
|
<input ref={$upload} accept="image/*" type="file" hidden onChange={handleFile} />
|
||||||
) : (
|
</Spin>
|
||||||
<LazyLoadImage src={src} alt={alt} width={width} height={height} />
|
</div>
|
||||||
)}
|
) : (
|
||||||
</Resizeable>
|
<LazyLoadImage src={src} alt={alt} width={width} height={height} />
|
||||||
</DragableWrapper>
|
)}
|
||||||
|
</Resizeable>
|
||||||
|
</div>
|
||||||
|
</NodeViewWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
|
import { NodeViewWrapper } from '@tiptap/react';
|
||||||
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 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 styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
|
@ -35,15 +34,16 @@ export const KatexWrapper = ({ node, editor }) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DragableWrapper
|
<NodeViewWrapper
|
||||||
editor={editor}
|
className={'drag-container render-wrapper'}
|
||||||
extensionName={Katex.name}
|
|
||||||
className={'render-wrapper'}
|
|
||||||
style={{
|
style={{
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className={styles.wrap}>{content}</div>
|
<div className={'drag-handle'} contentEditable="false" draggable="true" data-drag-handle />
|
||||||
</DragableWrapper>
|
<div className={'drag-content'}>
|
||||||
|
<div className={styles.wrap}>{content}</div>
|
||||||
|
</div>
|
||||||
|
</NodeViewWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Button, Space, Spin, Typography } from '@douyinfe/semi-ui';
|
import { Button, Space, Spin, Typography } from '@douyinfe/semi-ui';
|
||||||
|
import { NodeViewWrapper } from '@tiptap/react';
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import { IconMind, IconMindCenter, IconZoomIn, IconZoomOut } from 'components/icons';
|
import { IconMind, IconMindCenter, IconZoomIn, IconZoomOut } from 'components/icons';
|
||||||
import { Resizeable } from 'components/resizeable';
|
import { Resizeable } from 'components/resizeable';
|
||||||
|
@ -10,7 +11,6 @@ import VisibilitySensor from 'react-visibility-sensor';
|
||||||
import { load, renderMind } from 'thirtypart/kityminder';
|
import { load, renderMind } from 'thirtypart/kityminder';
|
||||||
import { Mind } from 'tiptap/core/extensions/mind';
|
import { Mind } from 'tiptap/core/extensions/mind';
|
||||||
import { MAX_ZOOM, MIN_ZOOM, ZOOM_STEP } from 'tiptap/core/menus/mind/constant';
|
import { MAX_ZOOM, MIN_ZOOM, ZOOM_STEP } from 'tiptap/core/menus/mind/constant';
|
||||||
import { DragableWrapper } from 'tiptap/core/wrappers/dragable';
|
|
||||||
import { clamp, getEditorContainerDOMSize } from 'tiptap/prose-utils';
|
import { clamp, getEditorContainerDOMSize } from 'tiptap/prose-utils';
|
||||||
|
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
@ -108,64 +108,69 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
}, [width, height, setCenter]);
|
}, [width, height, setCenter]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DragableWrapper
|
<NodeViewWrapper className={cls('drag-container', styles.wrap, isActive && styles.isActive)}>
|
||||||
editor={editor}
|
<div className={'drag-handle'} contentEditable="false" draggable="true" data-drag-handle />
|
||||||
extensionName={Mind.name}
|
<div className={'drag-content'}>
|
||||||
className={cls(styles.wrap, isActive && styles.isActive)}
|
<VisibilitySensor onChange={onViewportChange}>
|
||||||
>
|
<Resizeable isEditable={isEditable} width={width} height={height} maxWidth={maxWidth} onChangeEnd={onResize}>
|
||||||
<VisibilitySensor onChange={onViewportChange}>
|
<div
|
||||||
<Resizeable isEditable={isEditable} width={width} height={height} maxWidth={maxWidth} onChangeEnd={onResize}>
|
className={cls(styles.renderWrap, 'render-wrapper')}
|
||||||
<div
|
style={{ ...INHERIT_SIZE_STYLE, overflow: 'hidden' }}
|
||||||
className={cls(styles.renderWrap, 'render-wrapper')}
|
>
|
||||||
style={{ ...INHERIT_SIZE_STYLE, overflow: 'hidden' }}
|
{error && (
|
||||||
>
|
<div style={INHERIT_SIZE_STYLE}>
|
||||||
{error && (
|
<Text>{error.message || error}</Text>
|
||||||
<div style={INHERIT_SIZE_STYLE}>
|
</div>
|
||||||
<Text>{error.message || error}</Text>
|
)}
|
||||||
|
|
||||||
|
{loading && <Spin spinning style={INHERIT_SIZE_STYLE}></Spin>}
|
||||||
|
|
||||||
|
{!loading && !error && visible && (
|
||||||
|
<div style={{ height: '100%', maxHeight: '100%', overflow: 'hidden' }} ref={setMind}></div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={styles.title}>
|
||||||
|
<Space>
|
||||||
|
<span className={styles.icon}>
|
||||||
|
<IconMind />
|
||||||
|
</span>
|
||||||
|
思维导图
|
||||||
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{loading && <Spin spinning style={INHERIT_SIZE_STYLE}></Spin>}
|
<div className={styles.mindHandlerWrap}>
|
||||||
|
<Tooltip content="居中">
|
||||||
{!loading && !error && visible && (
|
<Button
|
||||||
<div style={{ height: '100%', maxHeight: '100%', overflow: 'hidden' }} ref={setMind}></div>
|
size="small"
|
||||||
)}
|
theme="borderless"
|
||||||
|
type="tertiary"
|
||||||
<div className={styles.title}>
|
icon={<IconMindCenter />}
|
||||||
<Space>
|
onClick={setCenter}
|
||||||
<span className={styles.icon}>
|
/>
|
||||||
<IconMind />
|
</Tooltip>
|
||||||
</span>
|
<Tooltip content="缩小">
|
||||||
思维导图
|
<Button
|
||||||
</Space>
|
size="small"
|
||||||
|
theme="borderless"
|
||||||
|
type="tertiary"
|
||||||
|
icon={<IconZoomOut />}
|
||||||
|
onClick={setZoom('minus')}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip content="放大">
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
theme="borderless"
|
||||||
|
type="tertiary"
|
||||||
|
icon={<IconZoomIn />}
|
||||||
|
onClick={setZoom('plus')}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</Resizeable>
|
||||||
<div className={styles.mindHandlerWrap}>
|
</VisibilitySensor>
|
||||||
<Tooltip content="居中">
|
</div>
|
||||||
<Button size="small" theme="borderless" type="tertiary" icon={<IconMindCenter />} onClick={setCenter} />
|
</NodeViewWrapper>
|
||||||
</Tooltip>
|
|
||||||
<Tooltip content="缩小">
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
theme="borderless"
|
|
||||||
type="tertiary"
|
|
||||||
icon={<IconZoomOut />}
|
|
||||||
onClick={setZoom('minus')}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip content="放大">
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
theme="borderless"
|
|
||||||
type="tertiary"
|
|
||||||
icon={<IconZoomIn />}
|
|
||||||
onClick={setZoom('plus')}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Resizeable>
|
|
||||||
</VisibilitySensor>
|
|
||||||
</DragableWrapper>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,7 +16,6 @@ import { Countdown } from 'tiptap/core/extensions/countdown';
|
||||||
import { Document } from 'tiptap/core/extensions/document';
|
import { Document } from 'tiptap/core/extensions/document';
|
||||||
import { DocumentChildren } from 'tiptap/core/extensions/document-children';
|
import { DocumentChildren } from 'tiptap/core/extensions/document-children';
|
||||||
import { DocumentReference } from 'tiptap/core/extensions/document-reference';
|
import { DocumentReference } from 'tiptap/core/extensions/document-reference';
|
||||||
import { Dragable } from 'tiptap/core/extensions/dragable';
|
|
||||||
import { Dropcursor } from 'tiptap/core/extensions/dropcursor';
|
import { Dropcursor } from 'tiptap/core/extensions/dropcursor';
|
||||||
import { Emoji } from 'tiptap/core/extensions/emoji';
|
import { Emoji } from 'tiptap/core/extensions/emoji';
|
||||||
import { EventEmitter } from 'tiptap/core/extensions/event-emitter';
|
import { EventEmitter } from 'tiptap/core/extensions/event-emitter';
|
||||||
|
@ -99,7 +98,6 @@ export const CollaborationKit = [
|
||||||
CodeBlock,
|
CodeBlock,
|
||||||
Color,
|
Color,
|
||||||
ColorHighlighter,
|
ColorHighlighter,
|
||||||
Dragable,
|
|
||||||
Dropcursor,
|
Dropcursor,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
Focus,
|
Focus,
|
||||||
|
|
Loading…
Reference in New Issue