From 68978407a802b3dfe7fbd52f35fa532acb6c305d Mon Sep 17 00:00:00 2001 From: Innei Date: Sun, 4 Feb 2024 00:04:26 +0800 Subject: [PATCH] feat: copy excalidraw data Signed-off-by: Innei --- .../ui/editor/Milkdown/plugins/Excalidraw.tsx | 158 +++++++++++------- .../plugins/__internal/SharedModalAction.tsx | 6 +- 2 files changed, 101 insertions(+), 63 deletions(-) diff --git a/src/components/ui/editor/Milkdown/plugins/Excalidraw.tsx b/src/components/ui/editor/Milkdown/plugins/Excalidraw.tsx index 988e5bb3c2..9da0747e3f 100644 --- a/src/components/ui/editor/Milkdown/plugins/Excalidraw.tsx +++ b/src/components/ui/editor/Milkdown/plugins/Excalidraw.tsx @@ -23,6 +23,7 @@ import { } from '@milkdown/utils' import { BlockLoading } from '~/components/modules/shared/BlockLoading' +import { StyledButton } from '~/components/ui/button' import { CheckBoxLabel } from '~/components/ui/checkbox' import { useModalStack } from '~/components/ui/modal' import { safeJsonParse } from '~/lib/helper' @@ -82,6 +83,29 @@ export const excalidrawSchema = $nodeSchema(id, () => { }, }, + parseDOM: [ + { + tag: `div[data-type="${id}"]`, + preserveWhitespace: 'full', + getAttrs: (dom) => { + return { + value: (dom as any)?.dataset?.value || '', + } + }, + }, + ], + toDOM: (node) => { + const code = node.attrs.value as string + + const dom = document.createElement('div') + dom.dataset.type = id + + dom.dataset.value = code + dom.textContent = code + + return dom + }, + parseMarkdown: { match: ({ type }) => type === id, runner: (state, node, type) => { @@ -178,6 +202,61 @@ const ExcalidrawBoard: FC = () => { const [editorOption, setEditorOption] = useAtom(excalidrawOptionAtom) const excalidrawRef = useRef(null) + + const getFinalSaveValue = async (): Promise => { + if (editorOption.delta) { + const currentData = valueRef.current + if (!currentData) { + toast.error('无法获取当前数据,更新失败') + return + } + + // 如果是增量存储 + + if (!initialContent) { + // 没有初始数据的话,直接刷新文件 Link + return fullFileUpdateAsLink() + } + + // 初始数据是嵌入式数据 + const isEmbeddedData = safeJsonParse(initialContent) + if (isEmbeddedData) { + // 如果是嵌入式数据,直接更新为 link + return fullFileUpdateAsLink() + } + + // 初始数据是文件链接 + const dataRefData = excalidrawRef.current?.getRefData() + + if (!dataRefData) { + toast.error('无法获取原始数据增量更新失败') + return + } + + const delta = diff(dataRefData, JSON.parse(currentData)) + const firstLine = initialContent.split('\n')[0] + return [firstLine, JSON.stringify(delta, null, 0)].join('\n') + } else if (editorOption.embed) { + return valueRef.current + } + + if (!editorOption.delta && !editorOption.embed) { + return fullFileUpdateAsLink() + } + + async function fullFileUpdateAsLink() { + // 更新为链接类型 + const currentData = valueRef.current + if (!currentData) return + + const file = new File([currentData], 'file.excalidraw', {}) + toast.info('正在上传文件') + const result = await uploadFileToServer(FileTypeEnum.File, file) + + toast.success('上传成功') + return `ref:file/${result.name}` + } + } return (
@@ -224,69 +303,28 @@ const ExcalidrawBoard: FC = () => { getValue={valueGetterRef.current} nodeCtx={nodeCtx} save={async () => { - if (editorOption.delta) { - const currentData = valueRef.current - if (!currentData) { - toast.error('无法获取当前数据,更新失败') - return - } - - // 如果是增量存储 - - if (!initialContent) { - // 没有初始数据的话,直接刷新文件 Link - return fullFileUpdateAsLink() - } - - // 初始数据是嵌入式数据 - const isEmbeddedData = safeJsonParse(initialContent) - if (isEmbeddedData) { - // 如果是嵌入式数据,直接更新为 link - return fullFileUpdateAsLink() - } - - // 初始数据是文件链接 - const dataRefData = excalidrawRef.current?.getRefData() - - if (!dataRefData) { - toast.error('无法获取原始数据增量更新失败') + const value = await getFinalSaveValue() + if (!value) return + nodeCtx.setAttrs({ value }) + }} + > + { + const value = await getFinalSaveValue() + if (!value) { + toast.error('无法获取当前数据') return } - - const delta = diff(dataRefData, JSON.parse(currentData)) - const firstLine = initialContent.split('\n')[0] - nodeCtx.setAttrs({ - value: [firstLine, JSON.stringify(delta, null, 0)].join( - '\n', - ), - }) - } else if (editorOption.embed) { - nodeCtx.setAttrs({ value: valueRef.current }) - } - - if (!editorOption.delta && !editorOption.embed) { - return fullFileUpdateAsLink() - } - - async function fullFileUpdateAsLink() { - // 更新为链接类型 - const currentData = valueRef.current - if (!currentData) return - - const file = new File([currentData], 'file.excalidraw', {}) - toast.info('正在上传文件') - const result = await uploadFileToServer( - FileTypeEnum.File, - file, + await navigator.clipboard.writeText( + `\`\`\`excalidraw\n${value}\n\`\`\``, ) - - toast.success('上传成功') - nodeCtx.setAttrs({ - value: `ref:file/${result.name}`, - }) - } - }} - /> + toast.success('已复制') + }} + > + 复制 + +
diff --git a/src/components/ui/editor/Milkdown/plugins/__internal/SharedModalAction.tsx b/src/components/ui/editor/Milkdown/plugins/__internal/SharedModalAction.tsx index 897bfe05ce..4b5032066c 100644 --- a/src/components/ui/editor/Milkdown/plugins/__internal/SharedModalAction.tsx +++ b/src/components/ui/editor/Milkdown/plugins/__internal/SharedModalAction.tsx @@ -1,6 +1,5 @@ import { useState } from 'react' import type { NodeViewContext } from '@prosemirror-adapter/react' -import type { FC } from 'react' import { schemaCtx } from '@milkdown/core' @@ -9,12 +8,12 @@ import { useCurrentModal } from '~/components/ui/modal' import { useEditorCtx } from '../../ctx' -export const SharedModalAction: FC<{ +export const SharedModalAction: Component<{ nodeCtx: NodeViewContext getValue(): string | undefined save?: (value: string) => Promise | void -}> = ({ nodeCtx, getValue, save }) => { +}> = ({ nodeCtx, getValue, save, children }) => { const { getPos, view, node } = nodeCtx const { dismiss } = useCurrentModal() const ctx = useEditorCtx() @@ -29,6 +28,7 @@ export const SharedModalAction: FC<{ const [waiting, setWaiting] = useState(false) return (
+ {children} 删除