diff --git a/packages/basic-modules/src/constants/icon-svg.ts b/packages/basic-modules/src/constants/icon-svg.ts
index a228db90d..7fef44389 100644
--- a/packages/basic-modules/src/constants/icon-svg.ts
+++ b/packages/basic-modules/src/constants/icon-svg.ts
@@ -154,3 +154,7 @@ export const CHECK_BOX_SVG =
// 回车
export const ENTER_SVG =
''
+
+// 格式刷
+export const FORMAT_PAINTER =
+ ''
diff --git a/packages/basic-modules/src/index.ts b/packages/basic-modules/src/index.ts
index 32d518b61..4988f543b 100644
--- a/packages/basic-modules/src/index.ts
+++ b/packages/basic-modules/src/index.ts
@@ -26,6 +26,7 @@ import wangEditorDividerModule from './modules/divider'
import wangEditorCodeBlockModule from './modules/code-block'
import wangEditorFullScreenModule from './modules/full-screen'
import wangEditorCommonModule from './modules/common'
+import wangEditorFormatPainterModule from './modules/format-painter'
export default [
// text style
@@ -54,6 +55,7 @@ export default [
wangEditorTodoModule,
// command
+ wangEditorFormatPainterModule,
wangEditorUndoRedoModule,
wangEditorFullScreenModule,
wangEditorCommonModule,
diff --git a/packages/basic-modules/src/locale/en.ts b/packages/basic-modules/src/locale/en.ts
index 311a0fd98..f2f795a0d 100644
--- a/packages/basic-modules/src/locale/en.ts
+++ b/packages/basic-modules/src/locale/en.ts
@@ -97,4 +97,7 @@ export default {
todo: {
todo: 'Todo',
},
+ formatPainter: {
+ title: 'Format Painter',
+ },
}
diff --git a/packages/basic-modules/src/locale/zh-CN.ts b/packages/basic-modules/src/locale/zh-CN.ts
index 4e860c987..6349197d8 100644
--- a/packages/basic-modules/src/locale/zh-CN.ts
+++ b/packages/basic-modules/src/locale/zh-CN.ts
@@ -97,4 +97,7 @@ export default {
todo: {
todo: '待办',
},
+ formatPainter: {
+ title: '格式刷',
+ },
}
diff --git a/packages/basic-modules/src/modules/format-painter/helper.ts b/packages/basic-modules/src/modules/format-painter/helper.ts
new file mode 100644
index 000000000..fe12933f7
--- /dev/null
+++ b/packages/basic-modules/src/modules/format-painter/helper.ts
@@ -0,0 +1,13 @@
+import { IDomEditor } from '@wangeditor-next/core'
+import { SlateEditor } from '@wangeditor-next/editor'
+
+/** 清空所有标记(文本样式) */
+export function clearAllMarks(editor: IDomEditor) {
+ const marks = SlateEditor.marks(editor)
+
+ if (marks) {
+ Object.keys(marks).forEach(mark => {
+ editor.removeMark(mark)
+ })
+ }
+}
diff --git a/packages/basic-modules/src/modules/format-painter/index.ts b/packages/basic-modules/src/modules/format-painter/index.ts
new file mode 100644
index 000000000..54a52ed34
--- /dev/null
+++ b/packages/basic-modules/src/modules/format-painter/index.ts
@@ -0,0 +1,15 @@
+/**
+ * @description format painter
+ * @author CodePencil
+ */
+
+import { IModuleConf } from '@wangeditor-next/core'
+import { formatPainterConf } from './menu/index'
+import withFormatPainter from './plugin'
+
+const formatPainter: Partial = {
+ menus: [formatPainterConf],
+ editorPlugin: withFormatPainter,
+}
+
+export default formatPainter
diff --git a/packages/basic-modules/src/modules/format-painter/menu/FormatPainter.ts b/packages/basic-modules/src/modules/format-painter/menu/FormatPainter.ts
new file mode 100644
index 000000000..9723dd3a1
--- /dev/null
+++ b/packages/basic-modules/src/modules/format-painter/menu/FormatPainter.ts
@@ -0,0 +1,67 @@
+/**
+ * @description Format Painter
+ * @author CodePencil
+ */
+
+import { IButtonMenu, IDomEditor, t } from '@wangeditor-next/core'
+import { SlateEditor } from '@wangeditor-next/editor'
+import { FORMAT_PAINTER } from '../../../constants/icon-svg'
+import { Text } from 'slate'
+import { clearAllMarks } from '../helper'
+
+interface FormatPaintAttributes {
+ isSelect: boolean
+ formatStyle: Omit | null
+}
+
+class FormatPainter implements IButtonMenu {
+ title = t('formatPainter.title')
+ iconSvg = FORMAT_PAINTER
+ tag = 'button'
+ static attrs: FormatPaintAttributes = {
+ isSelect: false,
+ formatStyle: null,
+ }
+
+ getValue(editor: IDomEditor): string | boolean {
+ return ''
+ }
+
+ isActive(editor: IDomEditor): boolean {
+ return FormatPainter.attrs.isSelect
+ }
+
+ isDisabled(editor: IDomEditor): boolean {
+ return false
+ }
+
+ setFormatHtml(editor: IDomEditor) {
+ if (!editor.getSelectionText().length) return
+ if (FormatPainter.attrs.formatStyle) {
+ clearAllMarks(editor)
+ for (const [key, value] of Object.entries(FormatPainter.attrs.formatStyle)) {
+ editor.addMark(key, value)
+ }
+ }
+ FormatPainter.attrs.formatStyle = {}
+ FormatPainter.attrs.isSelect = false
+ }
+
+ exec(editor: IDomEditor, value: string | boolean) {
+ // 如果已经选中了格式刷则取消选中,反之保存已经选中文本的样式
+ if (FormatPainter.attrs.isSelect) {
+ FormatPainter.attrs.isSelect = false
+ } else {
+ // 判断是否选中文本
+ if (editor.getSelectionText().length) {
+ FormatPainter.attrs.formatStyle = SlateEditor.marks(editor)
+ FormatPainter.attrs.isSelect = true
+ }
+ }
+
+ editor.blur()
+ editor.focus()
+ }
+}
+
+export default FormatPainter
diff --git a/packages/basic-modules/src/modules/format-painter/menu/index.ts b/packages/basic-modules/src/modules/format-painter/menu/index.ts
new file mode 100644
index 000000000..c18644f6c
--- /dev/null
+++ b/packages/basic-modules/src/modules/format-painter/menu/index.ts
@@ -0,0 +1,13 @@
+/**
+ * @description menu entry
+ * @author CodePencil
+ */
+
+import FormatPainter from './FormatPainter'
+
+export const formatPainterConf = {
+ key: 'formatPainter',
+ factory() {
+ return new FormatPainter()
+ },
+}
diff --git a/packages/basic-modules/src/modules/format-painter/plugin.ts b/packages/basic-modules/src/modules/format-painter/plugin.ts
new file mode 100644
index 000000000..229f13a70
--- /dev/null
+++ b/packages/basic-modules/src/modules/format-painter/plugin.ts
@@ -0,0 +1,34 @@
+/**
+ * @description editor 插件,重写 editor API
+ * @author CodePencil
+ */
+
+import { IDomEditor } from '@wangeditor-next/core'
+import FormatPainter from './menu/FormatPainter'
+
+function withFormatPainter(editor: T): T {
+ const formatPainter = new FormatPainter()
+
+ const { onChange } = editor
+ const newEditor = editor
+
+ const handleMouseUp = () => {
+ formatPainter.setFormatHtml(newEditor)
+ document.removeEventListener('mouseup', handleMouseUp)
+ }
+
+ newEditor.onChange = () => {
+ onChange()
+
+ if (FormatPainter.attrs.isSelect) {
+ // 避免重复绑定 mouseup 事件
+ document.removeEventListener('mouseup', handleMouseUp)
+ document.addEventListener('mouseup', handleMouseUp)
+ }
+ }
+
+ // 返回 editor ,重要!
+ return newEditor
+}
+
+export default withFormatPainter
diff --git a/packages/editor/src/init-default-config/config/hoverbar.ts b/packages/editor/src/init-default-config/config/hoverbar.ts
index 718318e57..ff9c77d76 100644
--- a/packages/editor/src/init-default-config/config/hoverbar.ts
+++ b/packages/editor/src/init-default-config/config/hoverbar.ts
@@ -55,6 +55,7 @@ export function genDefaultHoverbarKeys() {
// 也可以自定义 match 来匹配元素,此时 key 就随意了
text: {
menuKeys: [
+ 'formatPainter',
'headerSelect',
'insertLink',
'bulletedList',
diff --git a/packages/editor/src/init-default-config/config/toolbar.ts b/packages/editor/src/init-default-config/config/toolbar.ts
index 27a1d7a1a..bac3479b4 100644
--- a/packages/editor/src/init-default-config/config/toolbar.ts
+++ b/packages/editor/src/init-default-config/config/toolbar.ts
@@ -82,6 +82,7 @@ export function genDefaultToolbarKeys() {
'undo',
'redo',
'|',
+ 'formatPainter',
'fullScreen',
]
}