From 88eee05710e53dbc44c472baa349ad6fd1d0dd12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?wifi=E6=AD=AAf?= <1402772884@qq.com> Date: Sat, 8 Feb 2025 01:31:37 +0800 Subject: [PATCH 1/2] =?UTF-8?q?docs(mini-markdown-docs):=20=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=96=87=E6=A1=A3api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/guide/api.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/api.mdx b/docs/guide/api.mdx index 9c8822e..bad6337 100644 --- a/docs/guide/api.mdx +++ b/docs/guide/api.mdx @@ -184,7 +184,7 @@ type Callback = (param: { url: string; alt?: string }) => void; ### onPatseUpload -同 `onDragUpload` 事件。 +粘贴上传图片时触发,同 `onDragUpload` 事件。 ## 类型 -- Gitee From 36c7cd7e360fa4a1b07741ec6e634dc00d3bf6f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?wifi=E6=AD=AAf?= <1402772884@qq.com> Date: Sat, 8 Feb 2025 04:00:10 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat(mini-markdown-editor):=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E7=BC=96=E8=BE=91=E5=99=A8ref=E5=B1=9E=E6=80=A7?= =?UTF-8?q?=EF=BC=8C=E6=96=B0=E5=A2=9E=E6=96=87=E6=A1=A3=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/example/index.mdx | 1 - docs/example/index.tsx | 150 ++++++++++++++++++ docs/package.json | 3 +- packages/mini-markdown-editor/src/App.tsx | 90 ++++++++++- .../src/EditorWrapper.tsx | 11 +- .../src/hooks/use-expose-handle.ts | 98 ++++++++++++ packages/mini-markdown-editor/src/index.tsx | 2 +- .../src/types/global-config.ts | 5 + .../mini-markdown-editor/src/types/ref.ts | 40 +++++ 9 files changed, 388 insertions(+), 12 deletions(-) delete mode 100644 docs/example/index.mdx create mode 100644 docs/example/index.tsx create mode 100644 packages/mini-markdown-editor/src/hooks/use-expose-handle.ts create mode 100644 packages/mini-markdown-editor/src/types/ref.ts diff --git a/docs/example/index.mdx b/docs/example/index.mdx deleted file mode 100644 index c6fba08..0000000 --- a/docs/example/index.mdx +++ /dev/null @@ -1 +0,0 @@ -# example \ No newline at end of file diff --git a/docs/example/index.tsx b/docs/example/index.tsx new file mode 100644 index 0000000..c78e7fa --- /dev/null +++ b/docs/example/index.tsx @@ -0,0 +1,150 @@ +import React, { FC, useEffect, useRef, useState } from "react"; +import { Editor } from "@mini-markdown/editor"; +import type { Callback, EditorRef } from "@mini-markdown/editor"; +import { Button, message } from "antd"; +// 可根据需要引入不同的主题 +import "highlight.js/styles/atom-one-dark.css"; +import { ViewUpdate } from "@mini-markdown/editor"; + +export const frontmatter = { + // 声明布局类型 + pageType: "page", +}; + +const App: FC = () => { + // 请求测试 + const handleUpload = async (file: File, callback: Callback) => { + await new Promise((resolve) => { + setTimeout(() => { + console.log("settimeout 上传成功", file); + resolve({}); + }, 1500); + }); + callback({ + url: "https://www.baidu.com/img/flexible/logo/pc/result@2.png", + alt: "alt", + }); + message.success("上传成功"); + }; + + const [theme, setTheme] = useState<"light" | "dark">("light"); + const changeTheme = () => { + setTheme(theme === "light" ? "dark" : "light"); + }; + + const handleChange = (val: string, view: ViewUpdate) => { + console.log(val, view); + }; + + const handlePatseUpload = async (file: File, callback: Callback) => { + await new Promise((resolve) => { + setTimeout(() => { + console.log("settimeout 上传成功", file); + resolve({}); + }, 1500); + }); + callback({ + url: "https://www.baidu.com/img/flexible/logo/pc/result@2.png", + alt: "123", + }); + message.success("上传成功"); + }; + + const editorRef = useRef(null); + + useEffect(() => { + if (editorRef.current) { + console.log(editorRef.current); + } + }, []); + + return ( + <> + + + + + + + + + + + + + ); +}; + +export default App; diff --git a/docs/package.json b/docs/package.json index ea83568..c5a6144 100644 --- a/docs/package.json +++ b/docs/package.json @@ -13,6 +13,7 @@ "rspress": "^1.41.0" }, "dependencies": { - "@mini-markdown/editor": "workspace:^0.0.0" + "@mini-markdown/editor": "workspace:^0.0.0", + "antd": "^5.23.2" } } \ No newline at end of file diff --git a/packages/mini-markdown-editor/src/App.tsx b/packages/mini-markdown-editor/src/App.tsx index e9684ad..2e3fc39 100644 --- a/packages/mini-markdown-editor/src/App.tsx +++ b/packages/mini-markdown-editor/src/App.tsx @@ -1,12 +1,13 @@ -import { FC, useState } from "react"; +import { FC, useEffect, useRef, useState } from "react"; import styled from "styled-components"; import EditorWrapper from "./EditorWrapper"; import { Callback } from "./types/global-config"; import { Button, message } from "antd"; // 可根据需要引入不同的主题 import "highlight.js/styles/atom-one-dark.css"; -import { ViewUpdate } from "./types/code-mirror"; +// import { ViewUpdate } from "./types/code-mirror"; import { EditorView } from "@uiw/react-codemirror"; +import { EditorRef } from "./types/ref"; const AppWrapper = styled.div` width: 100%; @@ -40,9 +41,9 @@ const App: FC = () => { setTheme(theme === "light" ? "dark" : "light"); }; - const handleChange = (val: string, view: ViewUpdate) => { - console.log(val, view); - }; + // const handleChange = (val: string, view: ViewUpdate) => { + // // console.log(val, view); + // }; const handleSave = (content: string, view: EditorView) => { console.log(content, view); @@ -62,19 +63,96 @@ const App: FC = () => { message.success("上传成功"); }; + const editorRef = useRef(null); + + useEffect(() => { + if (editorRef.current) { + console.log(editorRef.current); + } + }, []); + return ( + + + + + + + + + ); diff --git a/packages/mini-markdown-editor/src/EditorWrapper.tsx b/packages/mini-markdown-editor/src/EditorWrapper.tsx index d127f39..047e75f 100644 --- a/packages/mini-markdown-editor/src/EditorWrapper.tsx +++ b/packages/mini-markdown-editor/src/EditorWrapper.tsx @@ -1,4 +1,4 @@ -import { FC, Fragment, useDeferredValue } from "react"; +import { FC, forwardRef, Fragment, useDeferredValue } from "react"; import styled from "styled-components"; import { useEditorContentStore } from "@/store/editor"; import Toolbar from "@/components/Toolbar"; @@ -13,6 +13,8 @@ import { GlobalConfig } from "./types/global-config"; import { useToolbarStore } from "./store/toolbar"; import { useInitSyncScrollStatus } from "./hooks/use-init-sync-scroll-status"; import GlobalTheme from "./theme/global-theme"; +import { EditorRef } from "./types/ref"; +import { useExposeHandle } from "./hooks/use-expose-handle"; const Container = styled.div` width: 100%; @@ -146,12 +148,15 @@ const RenderRow: FC<{ ); }; -const EditorWrapper: FC = (config) => { +const EditorWrapper = forwardRef((config, ref) => { const content = useEditorContentStore((state) => state.content); const deferredContent = useDeferredValue(content); const isFullScreen = useToolbarStore((state) => state.isFullScreen); const { isSyncScroll, updateSyncScrollStatus } = useInitSyncScrollStatus(); + // 外部ref使用的方法 + useExposeHandle(ref); + return ( @@ -184,6 +189,6 @@ const EditorWrapper: FC = (config) => { ); -}; +}); export default EditorWrapper; diff --git a/packages/mini-markdown-editor/src/hooks/use-expose-handle.ts b/packages/mini-markdown-editor/src/hooks/use-expose-handle.ts new file mode 100644 index 0000000..edee6c1 --- /dev/null +++ b/packages/mini-markdown-editor/src/hooks/use-expose-handle.ts @@ -0,0 +1,98 @@ +import { useEditorContentStore } from "@/store/editor"; +import { EditorRef } from "@/types/ref"; +import { insertContent } from "@/utils/insert-content"; +import { EditorView } from "@uiw/react-codemirror"; +import { useImperativeHandle, ForwardedRef } from "react"; + +class ExposeHandle { + private view: EditorView | null = null; + constructor(view: EditorView | null) { + if (!view) return; + this.view = view; + } + // 设置内容 + public setContent(content: string) { + if (!this.view) return; + this.view.focus(); + const selection = { + anchor: content.length, + head: content.length, + }; + insertContent.insertContent(content, selection); + } + // 获取内容 + public getContent() { + if (!this.view) return ""; + return this.view.state.doc.toString(); + } + // 清空内容 + public clear() { + if (!this.view) return; + this.view.dispatch({ + changes: { + from: 0, + to: this.view.state.doc.length, + insert: "", + }, + }); + } + // 设置光标位置 + public setCursor(start: number, end: number) { + if (!this.view) return; + if (start < end) { + new Error("start 必须比 end 大"); + return; + } + // 获取光标位置 + const { from, to } = this.view.state.selection.ranges[0]; + if (from < start || to < end) { + start = from; + end = to; + } + this.view.dispatch({ + selection: { + anchor: start, + head: end, + }, + }); + this.view.focus(); + } + // 获取光标位置 + public getCursor() { + if (!this.view) + return { + from: 0, + to: 0, + }; + const { from, to } = this.view.state.selection.ranges[0]; + return { from, to }; + } + // 获取选中内容 + public getSelection() { + if (!this.view) return ""; + const range = this.view.state.selection.main; + return this.view.state.sliceDoc(range.from, range.to); + } + // 聚焦 + public focus() { + if (!this.view) return; + this.view.focus(); + } +} + +// 向外部暴露方法,通过ref获取方法 +export const useExposeHandle = (ref: ForwardedRef) => { + const { editorView, previewView } = useEditorContentStore(); + const exposeFn = new ExposeHandle(editorView); + useImperativeHandle(ref, () => ({ + setContent: (content: string) => exposeFn.setContent(content), + getContent: () => exposeFn.getContent(), + clear: () => exposeFn.clear(), + setCursor: (start: number, end: number) => exposeFn.setCursor(start, end), + getCursor: () => exposeFn.getCursor(), + getSelection: () => exposeFn.getSelection(), + focus: () => exposeFn.focus(), + getEditorInstance: () => editorView, + getPreviewInstance: () => previewView, + })); +}; diff --git a/packages/mini-markdown-editor/src/index.tsx b/packages/mini-markdown-editor/src/index.tsx index c61cf87..0dccd3d 100644 --- a/packages/mini-markdown-editor/src/index.tsx +++ b/packages/mini-markdown-editor/src/index.tsx @@ -1,7 +1,6 @@ /** * 该文件用作打包时的入口文件 */ - import Editor from "./EditorWrapper"; // 导出组件 export { Editor }; @@ -10,3 +9,4 @@ export { Editor }; export * from "@/types/code-mirror"; export * from "@/types/global-config"; export * from "@/types/toolbar"; +export * from "@/types/ref"; diff --git a/packages/mini-markdown-editor/src/types/global-config.ts b/packages/mini-markdown-editor/src/types/global-config.ts index d6e626d..6b5a582 100644 --- a/packages/mini-markdown-editor/src/types/global-config.ts +++ b/packages/mini-markdown-editor/src/types/global-config.ts @@ -2,6 +2,11 @@ import { EditorView, ViewUpdate } from "@uiw/react-codemirror"; import { ToolbarType } from "./toolbar"; export interface GlobalConfig { + /** + * 编辑器内容 + * @type {string} + */ + initialValue?: string; /** * 需要渲染的 toolbar,默认全部渲染 * @type {ToolbarType[]} 需要渲染的 toolbar 数组 diff --git a/packages/mini-markdown-editor/src/types/ref.ts b/packages/mini-markdown-editor/src/types/ref.ts new file mode 100644 index 0000000..34c7e2e --- /dev/null +++ b/packages/mini-markdown-editor/src/types/ref.ts @@ -0,0 +1,40 @@ +import { EditorView } from "@uiw/react-codemirror"; + +export interface EditorRef { + /** + * 获取编辑器内容 + */ + getContent: () => string; + /** + * 设置编辑器内容 + */ + setContent: (content: string) => void; + /** + * 清空编辑器内容 + */ + clear: () => void; + /** + * 设置光标位置 + */ + setCursor: (start: number, end: number) => void; + /** + * 获取光标位置 + */ + getCursor: () => { from: number; to: number }; + /** + * 获取选中内容 + */ + getSelection: () => string; + /** + * 聚焦 + */ + focus: () => void; + /** + * 获取编辑器实例 + */ + getEditorInstance: () => EditorView | null; + /** + * 获取预览区实例 + */ + getPreviewInstance: () => HTMLElement | null; +} -- Gitee