From 10229ec8bc1293f6c57ea5b8ddb5f53a2b3d679a Mon Sep 17 00:00:00 2001 From: pipipi-pikachu Date: Sat, 17 Sep 2022 17:51:54 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=96=87=E5=AD=97?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useTextFormatPainter.ts | 28 +++++++++ src/plugins/icon.ts | 2 + src/store/main.ts | 8 ++- src/types/edit.ts | 14 ++++- src/utils/prosemirror/utils.ts | 10 ++-- src/views/Editor/Canvas/index.vue | 13 ++++- .../ElementStylePanel/MultiStylePanel.vue | 23 ++------ .../ElementStylePanel/ShapeStylePanel.vue | 46 +++++++++------ .../ElementStylePanel/TableStylePanel.vue | 28 +++------ .../ElementStylePanel/TextStylePanel.vue | 51 ++++++++-------- .../Editor/Toolbar/common/TextColorButton.vue | 38 ++++++++++++ .../components/element/ProsemirrorEditor.vue | 58 +++++++++++++------ 12 files changed, 209 insertions(+), 110 deletions(-) create mode 100644 src/hooks/useTextFormatPainter.ts create mode 100644 src/views/Editor/Toolbar/common/TextColorButton.vue diff --git a/src/hooks/useTextFormatPainter.ts b/src/hooks/useTextFormatPainter.ts new file mode 100644 index 00000000..48b6d5d8 --- /dev/null +++ b/src/hooks/useTextFormatPainter.ts @@ -0,0 +1,28 @@ +import { storeToRefs } from 'pinia' +import { useMainStore } from '@/store' + +export default () => { + const mainStore = useMainStore() + const { richTextAttrs, textFormatPainter } = storeToRefs(mainStore) + + const toggleFormatPainter = () => { + if (textFormatPainter.value) mainStore.setTextFormatPainter(null) + else { + mainStore.setTextFormatPainter({ + bold: richTextAttrs.value.bold, + em: richTextAttrs.value.em, + underline: richTextAttrs.value.underline, + strikethrough: richTextAttrs.value.strikethrough, + color: richTextAttrs.value.color, + backcolor: richTextAttrs.value.backcolor, + fontname: richTextAttrs.value.fontsize, + fontsize: richTextAttrs.value.fontsize, + align: richTextAttrs.value.align, + }) + } + } + + return { + toggleFormatPainter, + } +} \ No newline at end of file diff --git a/src/plugins/icon.ts b/src/plugins/icon.ts index 45f9b8a4..e2e87e68 100644 --- a/src/plugins/icon.ts +++ b/src/plugins/icon.ts @@ -109,6 +109,7 @@ import { Needle, TextRotationNone, TextRotationDown, + FormatBrush, } from '@icon-park/vue-next' export const icons = { @@ -219,6 +220,7 @@ export const icons = { IconNeedle: Needle, IconTextRotationNone: TextRotationNone, IconTextRotationDown: TextRotationDown, + IconFormatBrush: FormatBrush, } export default { diff --git a/src/store/main.ts b/src/store/main.ts index 196cfd42..77c053af 100644 --- a/src/store/main.ts +++ b/src/store/main.ts @@ -1,6 +1,6 @@ import { customAlphabet } from 'nanoid' import { defineStore } from 'pinia' -import { CreatingElement } from '@/types/edit' +import { CreatingElement, TextFormatPainter } from '@/types/edit' import { ToolbarStates } from '@/types/toolbar' import { DialogForExportTypes } from '@/types/export' import { SYS_FONTS } from '@/configs/font' @@ -31,6 +31,7 @@ export interface MainState { selectedSlidesIndex: number[] dialogForExport: DialogForExportTypes databaseId: string + textFormatPainter: TextFormatPainter | null } const nanoid = customAlphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz') @@ -59,6 +60,7 @@ export const useMainStore = defineStore('main', { selectedSlidesIndex: [], // 当前被选中的页面索引集合 dialogForExport: '', // 导出面板 databaseId, // 标识当前应用的indexedDB数据库ID + textFormatPainter: null, // 文字格式刷 }), getters: { @@ -160,5 +162,9 @@ export const useMainStore = defineStore('main', { setDialogForExport(type: DialogForExportTypes) { this.dialogForExport = type }, + + setTextFormatPainter(textFormatPainter: TextFormatPainter | null) { + this.textFormatPainter = textFormatPainter + }, }, }) \ No newline at end of file diff --git a/src/types/edit.ts b/src/types/edit.ts index 91202b6c..464ad2f5 100644 --- a/src/types/edit.ts +++ b/src/types/edit.ts @@ -90,4 +90,16 @@ export interface CreatingLineElement { type: 'line' data: LinePoolItem } -export type CreatingElement = CreatingTextElement | CreatingShapeElement | CreatingLineElement \ No newline at end of file +export type CreatingElement = CreatingTextElement | CreatingShapeElement | CreatingLineElement + +export interface TextFormatPainter { + bold?: boolean + em?: boolean + underline?: boolean + strikethrough?: boolean + color?: string + backcolor?: string + fontsize?: string + fontname?: string + align?: 'left' | 'right' | 'center' +} \ No newline at end of file diff --git a/src/utils/prosemirror/utils.ts b/src/utils/prosemirror/utils.ts index 265c0a9c..f47735c7 100644 --- a/src/utils/prosemirror/utils.ts +++ b/src/utils/prosemirror/utils.ts @@ -159,16 +159,18 @@ export const getAttrValueInSelection = (view: EditorView, attr: string) => { return value } +type Align = 'left' | 'right' | 'center' + interface DefaultAttrs { color?: string backcolor?: string fontsize?: string fontname?: string - align?: string + align?: Align } const _defaultAttrs: DefaultAttrs = { color: '#000', - backcolor: '#000', + backcolor: '', fontsize: '20px', fontname: '微软雅黑', align: 'left', @@ -190,7 +192,7 @@ export const getTextAttrs = (view: EditorView, defaultAttrs: DefaultAttrs = {}) const fontsize = getAttrValue(marks, 'fontsize', 'fontsize') || defaultAttrs.fontsize const fontname = getAttrValue(marks, 'fontname', 'fontname') || defaultAttrs.fontname const link = getAttrValue(marks, 'link', 'href') || '' - const align = getAttrValueInSelection(view, 'align') || defaultAttrs.align + const align = (getAttrValueInSelection(view, 'align') || defaultAttrs.align) as Align const isBulletList = isActiveOfParentNodeType('bullet_list', view.state) const isOrderedList = isActiveOfParentNodeType('ordered_list', view.state) const isBlockquote = isActiveOfParentNodeType('blockquote', view.state) @@ -232,7 +234,7 @@ export const defaultRichTextAttrs: TextAttrs = { subscript: false, code: false, color: '#000', - backcolor: '#000', + backcolor: '', fontsize: '20px', fontname: '微软雅黑', link: '', diff --git a/src/views/Editor/Canvas/index.vue b/src/views/Editor/Canvas/index.vue index faf5c569..a0380347 100644 --- a/src/views/Editor/Canvas/index.vue +++ b/src/views/Editor/Canvas/index.vue @@ -93,7 +93,7 @@ + + \ No newline at end of file diff --git a/src/views/components/element/ProsemirrorEditor.vue b/src/views/components/element/ProsemirrorEditor.vue index 9bd1c505..057eb077 100644 --- a/src/views/components/element/ProsemirrorEditor.vue +++ b/src/views/components/element/ProsemirrorEditor.vue @@ -1,6 +1,7 @@ @@ -14,7 +15,7 @@ import { EditorView } from 'prosemirror-view' import { toggleMark, wrapIn } from 'prosemirror-commands' import { initProsemirrorEditor, createDocument } from '@/utils/prosemirror' import { findNodesWithSameMark, getTextAttrs, autoSelectAll, addMark, markActive, getFontsize } from '@/utils/prosemirror/utils' -import emitter, { EmitterEvents, RichTextCommand } from '@/utils/emitter' +import emitter, { EmitterEvents, RichTextAction, RichTextCommand } from '@/utils/emitter' import { alignmentCommand } from '@/utils/prosemirror/commands/setTextAlign' import { indentCommand } from '@/utils/prosemirror/commands/setTextIndent' import { toggleList } from '@/utils/prosemirror/commands/toggleList' @@ -53,7 +54,7 @@ const emit = defineEmits<{ }>() const mainStore = useMainStore() -const { handleElementId } = storeToRefs(mainStore) +const { handleElementId, textFormatPainter } = storeToRefs(mainStore) const editorViewRef = ref() let editorView: EditorView @@ -104,23 +105,6 @@ watch(() => props.editable, () => { editorView.setProps({ editable: () => props.editable }) }) -// Prosemirror编辑器的初始化和卸载 -onMounted(() => { - editorView = initProsemirrorEditor((editorViewRef.value as Element), textContent.value, { - handleDOMEvents: { - focus: handleFocus, - blur: handleBlur, - keydown: handleKeydown, - click: handleClick, - }, - editable: () => props.editable, - }) - if (props.autoFocus) editorView.focus() -}) -onUnmounted(() => { - editorView && editorView.destroy() -}) - // 暴露 focus 方法 const focus = () => editorView.focus() defineExpose({ focus }) @@ -249,6 +233,38 @@ const execCommand = ({ target, action }: RichTextCommand) => { handleClick() } +// 鼠标抬起时,执行格式刷命令 +const handleMouseup = () => { + if (!textFormatPainter.value) return + + const actions: RichTextAction[] = [{ command: 'clear' }] + for (const key of Object.keys(textFormatPainter.value)) { + const command = key + const value = textFormatPainter.value[key] + if (value) actions.push({ command, value }) + } + execCommand({ action: actions }) + mainStore.setTextFormatPainter(null) +} + +// Prosemirror编辑器的初始化和卸载 +onMounted(() => { + editorView = initProsemirrorEditor((editorViewRef.value as Element), textContent.value, { + handleDOMEvents: { + focus: handleFocus, + blur: handleBlur, + keydown: handleKeydown, + click: handleClick, + mouseup: handleMouseup, + }, + editable: () => props.editable, + }) + if (props.autoFocus) editorView.focus() +}) +onUnmounted(() => { + editorView && editorView.destroy() +}) + emitter.on(EmitterEvents.RICH_TEXT_COMMAND, execCommand) onUnmounted(() => { emitter.off(EmitterEvents.RICH_TEXT_COMMAND, execCommand) @@ -258,5 +274,9 @@ onUnmounted(() => {