From c1a486ec8cbb8e1d955811ffd9b04bbf4b548e08 Mon Sep 17 00:00:00 2001 From: chenshuai2144 Date: Fri, 1 Nov 2024 01:33:42 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=BC=B9=E6=A1=86=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E5=92=8C=E4=BB=BB=E5=8A=A1=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/MarkdownEditor/demos/empty.tsx | 28 ++- src/MarkdownEditor/demos/minPreview.tsx | 58 +++++- .../editor/tools/ToolBar/BaseBar.tsx | 110 +--------- .../editor/tools/ToolBar/FloatBar.tsx | 51 +++-- .../editor/tools/ToolBar/ReadonlyBaseBar.tsx | 195 ++++++++++++++++++ .../editor/tools/ToolBar/floatBarStyle.ts | 7 +- src/MarkdownEditor/index.tsx | 2 +- 7 files changed, 317 insertions(+), 134 deletions(-) create mode 100644 src/MarkdownEditor/editor/tools/ToolBar/ReadonlyBaseBar.tsx diff --git a/src/MarkdownEditor/demos/empty.tsx b/src/MarkdownEditor/demos/empty.tsx index 7c82711e..c7d794a6 100644 --- a/src/MarkdownEditor/demos/empty.tsx +++ b/src/MarkdownEditor/demos/empty.tsx @@ -121,8 +121,18 @@ public class HelloWorld { upload: async (fileList) => { return new Promise((resolve) => { const file = fileList[0]; - const url = URL.createObjectURL(file); - resolve(url); + if (typeof file === 'string') { + fetch(file) + .then((res) => res.blob()) + .then((blob) => { + console.log(blob); + const url = URL.createObjectURL(blob); + resolve(url); + }); + } else { + const url = URL.createObjectURL(file); + resolve(url); + } }); }, }} @@ -178,8 +188,18 @@ public class HelloWorld { upload: async (fileList) => { return new Promise((resolve) => { const file = fileList[0]; - const url = URL.createObjectURL(file); - resolve(url); + if (typeof file === 'string') { + fetch(file) + .then((res) => res.blob()) + .then((blob) => { + console.log(blob); + const url = URL.createObjectURL(blob); + resolve(url); + }); + } else { + const url = URL.createObjectURL(file); + resolve(url); + } }); }, }} diff --git a/src/MarkdownEditor/demos/minPreview.tsx b/src/MarkdownEditor/demos/minPreview.tsx index 2ba39592..f22819ba 100644 --- a/src/MarkdownEditor/demos/minPreview.tsx +++ b/src/MarkdownEditor/demos/minPreview.tsx @@ -37,7 +37,6 @@ export default () => { style={{ height: '400vh', overflow: 'auto', - paddingTop: '400px', }} > { comment={{ enable: true, }} + readonly reportMode image={{ upload: async (fileList) => { return new Promise((resolve) => { const file = fileList[0]; - const url = URL.createObjectURL(file); - resolve(url); + if (typeof file === 'string') { + fetch(file) + .then((res) => res.blob()) + .then((blob) => { + console.log(blob); + const url = URL.createObjectURL(blob); + resolve(url); + }); + } else { + const url = URL.createObjectURL(file); + resolve(url); + } + }); + }, + }} + style={{ + width: '80%', + margin: '0 auto', + border: '1px solid #e8e8e8', + height: 'calc(100vh - 400px)', + }} + initValue={defaultValue} + /> + + Save + , + ], + }} + comment={{ + enable: true, + }} + reportMode + image={{ + upload: async (fileList) => { + return new Promise((resolve) => { + const file = fileList[0]; + if (typeof file === 'string') { + fetch(file) + .then((res) => res.blob()) + .then((blob) => { + console.log(blob); + const url = URL.createObjectURL(blob); + resolve(url); + }); + } else { + const url = URL.createObjectURL(file); + resolve(url); + } }); }, }} diff --git a/src/MarkdownEditor/editor/tools/ToolBar/BaseBar.tsx b/src/MarkdownEditor/editor/tools/ToolBar/BaseBar.tsx index 70309e4b..20a8518e 100644 --- a/src/MarkdownEditor/editor/tools/ToolBar/BaseBar.tsx +++ b/src/MarkdownEditor/editor/tools/ToolBar/BaseBar.tsx @@ -1,7 +1,6 @@ import { BoldOutlined, ClearOutlined, - CommentOutlined, ItalicOutlined, LinkOutlined, PlusCircleFilled, @@ -9,19 +8,15 @@ StrikethroughOutlined, UndoOutlined, } from '@ant-design/icons'; -import { ColorPicker, Divider, Dropdown, Input, Modal } from 'antd'; +import { ColorPicker, Divider, Dropdown } from 'antd'; import classnames from 'classnames'; import { runInAction } from 'mobx'; import { observer } from 'mobx-react-lite'; import React, { useCallback, useEffect, useMemo, useRef } from 'react'; -import { Editor, Element, Node, NodeEntry, Point, Transforms } from 'slate'; -import { CommentDataType, keyTask$ } from '../../../index'; +import { Editor, Element, NodeEntry } from 'slate'; +import { keyTask$ } from '../../../index'; import { useEditorStore } from '../../store'; -import { - EditorUtils, - getPointStrOffset, - getSelectionFromDomSelection, -} from '../../utils/editorUtils'; +import { EditorUtils } from '../../utils/editorUtils'; import { getInsertOptions } from '../InsertAutocomplete'; const HeatTextMap = { @@ -107,6 +102,7 @@ export const BaseToolBar = observer( showInsertAction?: boolean; extra?: React.ReactNode[]; min?: boolean; + readonly?: boolean; hashId?: string; hideTools?: ToolsKeyType[]; showEditor?: boolean; @@ -114,7 +110,7 @@ export const BaseToolBar = observer( const baseClassName = props.prefix || `toolbar-action`; const { hashId } = props; - const { store, readonly } = useEditorStore(); + const { store } = useEditorStore(); const [, setRefresh] = React.useState(false); const [highColor, setHighColor] = React.useState(null); @@ -298,99 +294,6 @@ export const BaseToolBar = observer( />, ); } - if (store?.editorProps?.comment?.enable && !props.showEditor) { - list.push( -
{ - const domSelection = window.getSelection(); - const editor = store?.editor; - let selection = editor.selection; - if (!selection) { - if (readonly && domSelection) { - selection = getSelectionFromDomSelection( - store?.editor, - domSelection!, - ); - } - - if (!selection) { - return; - } - } - - let texts: string[] = []; - let title = ''; - const fragments = Node.fragment(editor, selection); - for (let i = 0; i < fragments.length; i++) { - texts.push(Node.string(fragments[i])); - } - for (const str of texts) { - title += str; - } - const { focus, anchor } = selection; - const [start, end] = Point.isAfter(focus, anchor) - ? [anchor, focus] - : [focus, anchor]; - const anchorOffset = getPointStrOffset(editor, start); - const focusOffset = getPointStrOffset(editor, end); - - const comment: CommentDataType = { - selection: { anchor: start, focus: end }, - path: start.path, - time: Date.now(), - id: Date.now(), - content: '', - anchorOffset: anchorOffset, - focusOffset: focusOffset, - refContent: title, - commentType: 'comment', - }; - Modal.confirm({ - title: '添加评论', - content: ( - { - comment.content = e.target.value; - }} - /> - ), - icon: null, - onOk: async () => { - if (comment.content.trim() === '') { - return; - } - try { - await store?.editorProps?.comment?.onSubmit?.( - comment.id + '', - comment, - ); - // 更新时间戳,触发一下dom的rerender,不然不给我更新 - Transforms.setNodes( - editor, - { - updateTimestamp: Date.now(), - }, - { - at: comment.path, - }, - ); - } catch (error) {} - }, - }); - }} - > - -
, - ); - } list.push(
{ + console.log(l.key); return !props?.hideTools?.includes(l.key as ToolsKeyType); }); } diff --git a/src/MarkdownEditor/editor/tools/ToolBar/FloatBar.tsx b/src/MarkdownEditor/editor/tools/ToolBar/FloatBar.tsx index fd4e2b0b..c00500d8 100644 --- a/src/MarkdownEditor/editor/tools/ToolBar/FloatBar.tsx +++ b/src/MarkdownEditor/editor/tools/ToolBar/FloatBar.tsx @@ -9,13 +9,14 @@ import { getSelRect } from '../../utils/dom'; import { useLocalState } from '../../utils/useLocalState'; import { BaseToolBar } from './BaseBar'; import { useStyle } from './floatBarStyle'; +import { ReadonlyBaseBar } from './ReadonlyBaseBar'; const fileMap = new Map(); /** * 浮动工具栏,用于设置文本样式 */ -export const FloatBar = observer(() => { +export const FloatBar = observer((props: { readonly: boolean }) => { const { store } = useEditorStore(); const [state, setState] = useLocalState({ open: false, @@ -26,25 +27,32 @@ export const FloatBar = observer(() => { const sel = React.useRef(); - const resize = useCallback((force = false) => { - if (store.domRect && !store.openLinkPanel) { - let left = store.domRect.x; - left = left - (178 - store.domRect.width) / 2; - const container = store.container!; - if (left < 4) left = 4; - const barWidth = 232; - if (left > container.clientWidth - barWidth) - left = container.clientWidth - barWidth / 2; + const resize = useCallback( + (force = false) => { + if (store.domRect && !store.openLinkPanel) { + let left = store.domRect.x; + left = left - ((props.readonly ? 65 : 178) - store.domRect.width) / 2; - let top = state.open && !force ? state.top : store.domRect.top - 32; + console.log('store.domRect', store.domRect); - setState({ - open: true, - left: Math.max(left - container.getBoundingClientRect().left, 4), - top: Math.max(top - container.getBoundingClientRect().top, 4), - }); - } - }, []); + const container = store.container!; + if (left < 4) left = 4; + const barWidth = props.readonly ? 65 : 232; + + if (left > container.clientWidth - barWidth) + left = container.clientWidth - barWidth / 2; + + let top = state.open && !force ? state.top : store.domRect.top - 32; + + setState({ + open: true, + left: Math.max(left - container.getBoundingClientRect().left, 4), + top: Math.max(top - container.getBoundingClientRect().top, 4), + }); + } + }, + [props.readonly, state.open], + ); useEffect(() => { if (store.domRect && store.editor) { @@ -102,7 +110,6 @@ export const FloatBar = observer(() => { left: state.left, top: state.top, display: state.open ? undefined : 'none', - padding: 4, }} onMouseDown={(e) => { e.preventDefault(); @@ -110,7 +117,11 @@ export const FloatBar = observer(() => { }} className={classNames(baseClassName, hashId)} > - + {props.readonly ? ( + + ) : ( + + )}
, ); }); diff --git a/src/MarkdownEditor/editor/tools/ToolBar/ReadonlyBaseBar.tsx b/src/MarkdownEditor/editor/tools/ToolBar/ReadonlyBaseBar.tsx new file mode 100644 index 00000000..9d54c2b2 --- /dev/null +++ b/src/MarkdownEditor/editor/tools/ToolBar/ReadonlyBaseBar.tsx @@ -0,0 +1,195 @@ +import { CommentOutlined, CopyFilled } from '@ant-design/icons'; +import { Input, message, Modal } from 'antd'; +import classnames from 'classnames'; +import { observer } from 'mobx-react-lite'; +import React, { useEffect, useMemo } from 'react'; +import { Editor, Element, Node, Point, Transforms } from 'slate'; +import { CommentDataType } from '../../../index'; +import { useEditorStore } from '../../store'; +import { + getPointStrOffset, + getSelectionFromDomSelection, +} from '../../utils/editorUtils'; + +/** + * 复制基础栏 + * @param props + * @returns + */ +export const ReadonlyBaseBar = observer( + (props: { prefix?: string; hashId?: string }) => { + const baseClassName = props.prefix || `toolbar-action`; + const { hashId } = props; + + const { store } = useEditorStore(); + + const [, setRefresh] = React.useState(false); + + useEffect(() => { + setRefresh((r) => !r); + }, [store.refreshFloatBar]); + + /** + * 获取当前节点 + */ + const [node] = Editor.nodes(store?.editor, { + match: (n) => Element.isElement(n), + mode: 'lowest', + }); + + const listDom = useMemo(() => { + let list = []; + + if (store?.editorProps?.comment?.enable) { + list.push( +
{ + const domSelection = window.getSelection(); + const editor = store?.editor; + let selection = editor.selection; + if (!selection) { + if (domSelection) { + selection = getSelectionFromDomSelection( + store?.editor, + domSelection!, + ); + } + + if (!selection) { + return; + } + } + + let texts: string[] = []; + let title = ''; + const fragments = Node.fragment(editor, selection); + for (let i = 0; i < fragments.length; i++) { + texts.push(Node.string(fragments[i])); + } + for (const str of texts) { + title += str; + } + const { focus, anchor } = selection; + const [start, end] = Point.isAfter(focus, anchor) + ? [anchor, focus] + : [focus, anchor]; + const anchorOffset = getPointStrOffset(editor, start); + const focusOffset = getPointStrOffset(editor, end); + + const comment: CommentDataType = { + selection: { anchor: start, focus: end }, + path: start.path, + time: Date.now(), + id: Date.now(), + content: '', + anchorOffset: anchorOffset, + focusOffset: focusOffset, + refContent: title, + commentType: 'comment', + }; + Modal.confirm({ + title: '添加评论', + content: ( + { + comment.content = e.target.value; + }} + /> + ), + icon: null, + onOk: async () => { + if (comment.content.trim() === '') { + return; + } + try { + await store?.editorProps?.comment?.onSubmit?.( + comment.id + '', + comment, + ); + // 更新时间戳,触发一下dom的rerender,不然不给我更新 + Transforms.setNodes( + editor, + { + updateTimestamp: Date.now(), + }, + { + at: comment.path, + }, + ); + } catch (error) {} + }, + }); + }} + > + +
, + ); + } + + list.push( +
{ + const domSelection = window.getSelection(); + const editor = store?.editor; + let selection = editor.selection; + if (!selection) { + if (domSelection) { + selection = getSelectionFromDomSelection( + store?.editor, + domSelection!, + ); + } + + if (!selection) { + return; + } + } + + let texts: string[] = []; + let title = ''; + const fragments = Node.fragment(editor, selection); + for (let i = 0; i < fragments.length; i++) { + texts.push(Node.string(fragments[i])); + } + + for (const str of texts) { + title += str; + } + navigator.clipboard.writeText(title); + message.success('Copy Success'); + }} + > + + + +
, + ); + + return list; + }, [node]); + + return ( +
+ {listDom} +
+ ); + }, +); diff --git a/src/MarkdownEditor/editor/tools/ToolBar/floatBarStyle.ts b/src/MarkdownEditor/editor/tools/ToolBar/floatBarStyle.ts index 23085fa2..bf63eeaa 100644 --- a/src/MarkdownEditor/editor/tools/ToolBar/floatBarStyle.ts +++ b/src/MarkdownEditor/editor/tools/ToolBar/floatBarStyle.ts @@ -21,13 +21,14 @@ const genStyle: GenerateStyle = (token) => { padding: '4px 0', '&-item': { display: 'flex', - height: '32px', + height: '48px', alignItems: 'center', - gap: '4px', + gap: '8px', borderRadius: '4px', lineHeight: '32px', + fontSize: '16px', justifyContent: 'center', - padding: '0 4px', + padding: '0 8px', cursor: 'pointer', '&:hover': { backgroundColor: 'rgb(229 231 235 / 0.65)', diff --git a/src/MarkdownEditor/index.tsx b/src/MarkdownEditor/index.tsx index 8211f752..1e07d3af 100644 --- a/src/MarkdownEditor/index.tsx +++ b/src/MarkdownEditor/index.tsx @@ -372,7 +372,7 @@ export const MarkdownEditor: React.FC = (props) => { {...rest} instance={instance} /> - {readonly ? null : } + {readonly ? : } {instance.current && mount && toc !== false &&