feat: 添加文字格式刷

This commit is contained in:
pipipi-pikachu 2022-09-17 17:51:54 +08:00
parent 5b8695e9c0
commit 10229ec8bc
12 changed files with 209 additions and 110 deletions

View File

@ -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,
}
}

View File

@ -109,6 +109,7 @@ import {
Needle, Needle,
TextRotationNone, TextRotationNone,
TextRotationDown, TextRotationDown,
FormatBrush,
} from '@icon-park/vue-next' } from '@icon-park/vue-next'
export const icons = { export const icons = {
@ -219,6 +220,7 @@ export const icons = {
IconNeedle: Needle, IconNeedle: Needle,
IconTextRotationNone: TextRotationNone, IconTextRotationNone: TextRotationNone,
IconTextRotationDown: TextRotationDown, IconTextRotationDown: TextRotationDown,
IconFormatBrush: FormatBrush,
} }
export default { export default {

View File

@ -1,6 +1,6 @@
import { customAlphabet } from 'nanoid' import { customAlphabet } from 'nanoid'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { CreatingElement } from '@/types/edit' import { CreatingElement, TextFormatPainter } from '@/types/edit'
import { ToolbarStates } from '@/types/toolbar' import { ToolbarStates } from '@/types/toolbar'
import { DialogForExportTypes } from '@/types/export' import { DialogForExportTypes } from '@/types/export'
import { SYS_FONTS } from '@/configs/font' import { SYS_FONTS } from '@/configs/font'
@ -31,6 +31,7 @@ export interface MainState {
selectedSlidesIndex: number[] selectedSlidesIndex: number[]
dialogForExport: DialogForExportTypes dialogForExport: DialogForExportTypes
databaseId: string databaseId: string
textFormatPainter: TextFormatPainter | null
} }
const nanoid = customAlphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz') const nanoid = customAlphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')
@ -59,6 +60,7 @@ export const useMainStore = defineStore('main', {
selectedSlidesIndex: [], // 当前被选中的页面索引集合 selectedSlidesIndex: [], // 当前被选中的页面索引集合
dialogForExport: '', // 导出面板 dialogForExport: '', // 导出面板
databaseId, // 标识当前应用的indexedDB数据库ID databaseId, // 标识当前应用的indexedDB数据库ID
textFormatPainter: null, // 文字格式刷
}), }),
getters: { getters: {
@ -160,5 +162,9 @@ export const useMainStore = defineStore('main', {
setDialogForExport(type: DialogForExportTypes) { setDialogForExport(type: DialogForExportTypes) {
this.dialogForExport = type this.dialogForExport = type
}, },
setTextFormatPainter(textFormatPainter: TextFormatPainter | null) {
this.textFormatPainter = textFormatPainter
},
}, },
}) })

View File

@ -90,4 +90,16 @@ export interface CreatingLineElement {
type: 'line' type: 'line'
data: LinePoolItem data: LinePoolItem
} }
export type CreatingElement = CreatingTextElement | CreatingShapeElement | CreatingLineElement 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'
}

View File

@ -159,16 +159,18 @@ export const getAttrValueInSelection = (view: EditorView, attr: string) => {
return value return value
} }
type Align = 'left' | 'right' | 'center'
interface DefaultAttrs { interface DefaultAttrs {
color?: string color?: string
backcolor?: string backcolor?: string
fontsize?: string fontsize?: string
fontname?: string fontname?: string
align?: string align?: Align
} }
const _defaultAttrs: DefaultAttrs = { const _defaultAttrs: DefaultAttrs = {
color: '#000', color: '#000',
backcolor: '#000', backcolor: '',
fontsize: '20px', fontsize: '20px',
fontname: '微软雅黑', fontname: '微软雅黑',
align: 'left', align: 'left',
@ -190,7 +192,7 @@ export const getTextAttrs = (view: EditorView, defaultAttrs: DefaultAttrs = {})
const fontsize = getAttrValue(marks, 'fontsize', 'fontsize') || defaultAttrs.fontsize const fontsize = getAttrValue(marks, 'fontsize', 'fontsize') || defaultAttrs.fontsize
const fontname = getAttrValue(marks, 'fontname', 'fontname') || defaultAttrs.fontname const fontname = getAttrValue(marks, 'fontname', 'fontname') || defaultAttrs.fontname
const link = getAttrValue(marks, 'link', 'href') || '' 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 isBulletList = isActiveOfParentNodeType('bullet_list', view.state)
const isOrderedList = isActiveOfParentNodeType('ordered_list', view.state) const isOrderedList = isActiveOfParentNodeType('ordered_list', view.state)
const isBlockquote = isActiveOfParentNodeType('blockquote', view.state) const isBlockquote = isActiveOfParentNodeType('blockquote', view.state)
@ -232,7 +234,7 @@ export const defaultRichTextAttrs: TextAttrs = {
subscript: false, subscript: false,
code: false, code: false,
color: '#000', color: '#000',
backcolor: '#000', backcolor: '',
fontsize: '20px', fontsize: '20px',
fontname: '微软雅黑', fontname: '微软雅黑',
link: '', link: '',

View File

@ -93,7 +93,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { nextTick, onMounted, provide, ref, watch, watchEffect } from 'vue' import { nextTick, onMounted, onUnmounted, provide, ref, watch, watchEffect } from 'vue'
import { throttle } from 'lodash' import { throttle } from 'lodash'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { useMainStore, useSlidesStore, useKeyboardStore } from '@/store' import { useMainStore, useSlidesStore, useKeyboardStore } from '@/store'
@ -142,6 +142,7 @@ const {
showRuler, showRuler,
creatingElement, creatingElement,
canvasScale, canvasScale,
textFormatPainter,
} = storeToRefs(mainStore) } = storeToRefs(mainStore)
const { currentSlide } = storeToRefs(useSlidesStore()) const { currentSlide } = storeToRefs(useSlidesStore())
const { ctrlKeyState, spaceKeyState } = storeToRefs(useKeyboardStore()) const { ctrlKeyState, spaceKeyState } = storeToRefs(useKeyboardStore())
@ -190,17 +191,23 @@ onMounted(() => {
} }
}) })
// //
const handleClickBlankArea = (e: MouseEvent) => { const handleClickBlankArea = (e: MouseEvent) => {
mainStore.setActiveElementIdList([]) if (activeElementIdList.value.length) mainStore.setActiveElementIdList([])
if (!spaceKeyState.value) updateMouseSelection(e) if (!spaceKeyState.value) updateMouseSelection(e)
else dragViewport(e) else dragViewport(e)
if (!editorAreaFocus.value) mainStore.setEditorareaFocus(true) if (!editorAreaFocus.value) mainStore.setEditorareaFocus(true)
if (textFormatPainter.value) mainStore.setTextFormatPainter(null)
removeAllRanges() removeAllRanges()
} }
//
onUnmounted(() => {
if (textFormatPainter.value) mainStore.setTextFormatPainter(null)
})
// //
const removeEditorAreaFocus = () => { const removeEditorAreaFocus = () => {
if (editorAreaFocus.value) mainStore.setEditorareaFocus(false) if (editorAreaFocus.value) mainStore.setEditorareaFocus(false)

View File

@ -87,10 +87,9 @@
/> />
</template> </template>
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="文字颜色"> <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="文字颜色">
<Button class="text-color-btn" style="flex: 3;"> <TextColorButton :color="richTextAttrs.color" style="flex: 3;">
<IconText /> <IconText />
<div class="text-color-block" :style="{ backgroundColor: richTextAttrs.color }"></div> </TextColorButton>
</Button>
</Tooltip> </Tooltip>
</Popover> </Popover>
<Popover trigger="click"> <Popover trigger="click">
@ -101,10 +100,9 @@
/> />
</template> </template>
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="文字高亮"> <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="文字高亮">
<Button class="text-color-btn" style="flex: 3;"> <TextColorButton :color="richTextAttrs.backcolor" style="flex: 3;">
<IconHighLight /> <IconHighLight />
<div class="text-color-block" :style="{ backgroundColor: richTextAttrs.backcolor }"></div> </TextColorButton>
</Button>
</Tooltip> </Tooltip>
</Popover> </Popover>
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="增大字号"> <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="增大字号">
@ -151,6 +149,7 @@ import { WEB_FONTS } from '@/configs/font'
import useHistorySnapshot from '@/hooks/useHistorySnapshot' import useHistorySnapshot from '@/hooks/useHistorySnapshot'
import ColorButton from '../common/ColorButton.vue' import ColorButton from '../common/ColorButton.vue'
import TextColorButton from '../common/TextColorButton.vue'
const slidesStore = useSlidesStore() const slidesStore = useSlidesStore()
const { richTextAttrs, availableFonts, activeElementList } = storeToRefs(useMainStore()) const { richTextAttrs, availableFonts, activeElementList } = storeToRefs(useMainStore())
@ -251,18 +250,6 @@ const updateFontStyle = (command: string, value: string) => {
align-items: center; align-items: center;
margin-bottom: 10px; margin-bottom: 10px;
} }
.text-color-btn {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 0;
}
.text-color-block {
width: 16px;
height: 3px;
margin-top: 1px;
}
.font-size-btn { .font-size-btn {
padding: 0; padding: 0;
} }

View File

@ -111,10 +111,9 @@
/> />
</template> </template>
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="文字颜色"> <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="文字颜色">
<Button class="text-color-btn" style="flex: 3;"> <TextColorButton :color="richTextAttrs.color" style="flex: 3;">
<IconText /> <IconText />
<div class="text-color-block" :style="{ backgroundColor: richTextAttrs.color }"></div> </TextColorButton>
</Button>
</Tooltip> </Tooltip>
</Popover> </Popover>
<Popover trigger="click"> <Popover trigger="click">
@ -125,10 +124,9 @@
/> />
</template> </template>
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="文字高亮"> <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="文字高亮">
<Button class="text-color-btn" style="flex: 3;"> <TextColorButton :color="richTextAttrs.backcolor" style="flex: 3;">
<IconHighLight /> <IconHighLight />
<div class="text-color-block" :style="{ backgroundColor: richTextAttrs.backcolor }"></div> </TextColorButton>
</Button>
</Tooltip> </Tooltip>
</Popover> </Popover>
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="增大字号"> <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="增大字号">
@ -169,14 +167,33 @@
@click="emitRichTextCommand('underline')" @click="emitRichTextCommand('underline')"
><IconTextUnderline /></CheckboxButton> ><IconTextUnderline /></CheckboxButton>
</Tooltip> </Tooltip>
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="删除线">
<CheckboxButton
style="flex: 1;"
:checked="richTextAttrs.strikethrough"
@click="emitRichTextCommand('strikethrough')"
><IconStrikethrough /></CheckboxButton>
</Tooltip>
</CheckboxButtonGroup>
<CheckboxButtonGroup class="row">
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="清除格式"> <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="清除格式">
<CheckboxButton <CheckboxButton
style="flex: 1;" style="flex: 1;"
@click="emitRichTextCommand('clear')" @click="emitRichTextCommand('clear')"
><IconFormat /></CheckboxButton> ><IconFormat /></CheckboxButton>
</Tooltip> </Tooltip>
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="格式刷">
<CheckboxButton
style="flex: 1;"
:checked="!!textFormatPainter"
@click="toggleFormatPainter()"
><IconFormatBrush /></CheckboxButton>
</Tooltip>
</CheckboxButtonGroup> </CheckboxButtonGroup>
<Divider />
<RadioGroup <RadioGroup
class="row" class="row"
button-style="solid" button-style="solid"
@ -230,16 +247,18 @@ import { PPTShapeElement, ShapeGradient, ShapeText } from '@/types/slides'
import { WEB_FONTS } from '@/configs/font' import { WEB_FONTS } from '@/configs/font'
import emitter, { EmitterEvents } from '@/utils/emitter' import emitter, { EmitterEvents } from '@/utils/emitter'
import useHistorySnapshot from '@/hooks/useHistorySnapshot' import useHistorySnapshot from '@/hooks/useHistorySnapshot'
import useTextFormatPainter from '@/hooks/useTextFormatPainter'
import ElementOpacity from '../common/ElementOpacity.vue' import ElementOpacity from '../common/ElementOpacity.vue'
import ElementOutline from '../common/ElementOutline.vue' import ElementOutline from '../common/ElementOutline.vue'
import ElementShadow from '../common/ElementShadow.vue' import ElementShadow from '../common/ElementShadow.vue'
import ElementFlip from '../common/ElementFlip.vue' import ElementFlip from '../common/ElementFlip.vue'
import ColorButton from '../common/ColorButton.vue' import ColorButton from '../common/ColorButton.vue'
import TextColorButton from '../common/TextColorButton.vue'
const mainStore = useMainStore() const mainStore = useMainStore()
const slidesStore = useSlidesStore() const slidesStore = useSlidesStore()
const { handleElement, handleElementId, richTextAttrs, availableFonts } = storeToRefs(mainStore) const { handleElement, handleElementId, richTextAttrs, availableFonts, textFormatPainter } = storeToRefs(mainStore)
const handleShapeElement = handleElement as Ref<PPTShapeElement> const handleShapeElement = handleElement as Ref<PPTShapeElement>
@ -262,6 +281,7 @@ watch(handleElement, () => {
}, { deep: true, immediate: true }) }, { deep: true, immediate: true })
const { addHistorySnapshot } = useHistorySnapshot() const { addHistorySnapshot } = useHistorySnapshot()
const { toggleFormatPainter } = useTextFormatPainter()
const updateElement = (props: Partial<PPTShapeElement>) => { const updateElement = (props: Partial<PPTShapeElement>) => {
slidesStore.updateElement({ id: handleElementId.value, props }) slidesStore.updateElement({ id: handleElementId.value, props })
@ -323,18 +343,6 @@ const emitRichTextCommand = (command: string, value?: string) => {
align-items: center; align-items: center;
margin-bottom: 10px; margin-bottom: 10px;
} }
.text-color-btn {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 0;
}
.text-color-block {
width: 16px;
height: 3px;
margin-top: 1px;
}
.font-size-btn { .font-size-btn {
padding: 0; padding: 0;
} }

View File

@ -39,10 +39,9 @@
/> />
</template> </template>
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="文字颜色"> <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="文字颜色">
<Button class="text-color-btn" style="flex: 1;"> <TextColorButton :color="textAttrs.color" style="flex: 1;">
<IconText /> <IconText />
<div class="text-color-block" :style="{ backgroundColor: textAttrs.color }"></div> </TextColorButton>
</Button>
</Tooltip> </Tooltip>
</Popover> </Popover>
<Popover trigger="click"> <Popover trigger="click">
@ -53,10 +52,9 @@
/> />
</template> </template>
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="单元格填充"> <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="单元格填充">
<Button class="text-color-btn" style="flex: 1;"> <TextColorButton :color="textAttrs.backcolor" style="flex: 1;">
<IconFill /> <IconFill />
<div class="text-color-block" :style="{ backgroundColor: textAttrs.backcolor }"></div> </TextColorButton>
</Button>
</Tooltip> </Tooltip>
</Popover> </Popover>
</ButtonGroup> </ButtonGroup>
@ -196,6 +194,7 @@ import useHistorySnapshot from '@/hooks/useHistorySnapshot'
import ElementOutline from '../common/ElementOutline.vue' import ElementOutline from '../common/ElementOutline.vue'
import ColorButton from '../common/ColorButton.vue' import ColorButton from '../common/ColorButton.vue'
import TextColorButton from '../common/TextColorButton.vue'
const slidesStore = useSlidesStore() const slidesStore = useSlidesStore()
const { handleElement, handleElementId, selectedTableCells: selectedCells, availableFonts } = storeToRefs(useMainStore()) const { handleElement, handleElementId, selectedTableCells: selectedCells, availableFonts } = storeToRefs(useMainStore())
@ -211,7 +210,7 @@ const textAttrs = ref({
underline: false, underline: false,
strikethrough: false, strikethrough: false,
color: '#000', color: '#000',
backcolor: '#000', backcolor: '',
fontsize: '12px', fontsize: '12px',
fontname: '微软雅黑', fontname: '微软雅黑',
align: 'left', align: 'left',
@ -259,7 +258,7 @@ const updateTextAttrState = () => {
underline: false, underline: false,
strikethrough: false, strikethrough: false,
color: '#000', color: '#000',
backcolor: '#000', backcolor: '',
fontsize: '12px', fontsize: '12px',
fontname: '微软雅黑', fontname: '微软雅黑',
align: 'left', align: 'left',
@ -272,7 +271,7 @@ const updateTextAttrState = () => {
underline: !!style.underline, underline: !!style.underline,
strikethrough: !!style.strikethrough, strikethrough: !!style.strikethrough,
color: style.color || '#000', color: style.color || '#000',
backcolor: style.backcolor || '#000', backcolor: style.backcolor || '',
fontsize: style.fontsize || '12px', fontsize: style.fontsize || '12px',
fontname: style.fontname || '微软雅黑', fontname: style.fontname || '微软雅黑',
align: style.align || 'left', align: style.align || 'left',
@ -404,17 +403,6 @@ const setTableCol = (value: number) => {
.switch-wrapper { .switch-wrapper {
text-align: right; text-align: right;
} }
.text-color-btn {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.text-color-block {
width: 16px;
height: 3px;
margin-top: 1px;
}
.set-count { .set-count {
display: flex; display: flex;
justify-content: center; justify-content: center;

View File

@ -51,10 +51,9 @@
/> />
</template> </template>
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="文字颜色"> <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="文字颜色">
<Button class="text-color-btn" style="flex: 3;"> <TextColorButton :color="richTextAttrs.color" style="flex: 3;">
<IconText /> <IconText />
<div class="text-color-block" :style="{ backgroundColor: richTextAttrs.color }"></div> </TextColorButton>
</Button>
</Tooltip> </Tooltip>
</Popover> </Popover>
<Popover trigger="click"> <Popover trigger="click">
@ -65,10 +64,9 @@
/> />
</template> </template>
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="文字高亮"> <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="文字高亮">
<Button class="text-color-btn" style="flex: 3;"> <TextColorButton :color="richTextAttrs.backcolor" style="flex: 3;">
<IconHighLight /> <IconHighLight />
<div class="text-color-block" :style="{ backgroundColor: richTextAttrs.backcolor }"></div> </TextColorButton>
</Button>
</Tooltip> </Tooltip>
</Popover> </Popover>
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="增大字号"> <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="增大字号">
@ -116,12 +114,6 @@
@click="emitRichTextCommand('strikethrough')" @click="emitRichTextCommand('strikethrough')"
><IconStrikethrough /></CheckboxButton> ><IconStrikethrough /></CheckboxButton>
</Tooltip> </Tooltip>
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="清除格式">
<CheckboxButton
style="flex: 1;"
@click="emitRichTextCommand('clear')"
><IconFormat /></CheckboxButton>
</Tooltip>
</CheckboxButtonGroup> </CheckboxButtonGroup>
<CheckboxButtonGroup class="row"> <CheckboxButtonGroup class="row">
@ -153,6 +145,22 @@
@click="emitRichTextCommand('blockquote')" @click="emitRichTextCommand('blockquote')"
><IconQuote /></CheckboxButton> ><IconQuote /></CheckboxButton>
</Tooltip> </Tooltip>
</CheckboxButtonGroup>
<CheckboxButtonGroup class="row">
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="清除格式">
<CheckboxButton
style="flex: 1;"
@click="emitRichTextCommand('clear')"
><IconFormat /></CheckboxButton>
</Tooltip>
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="格式刷">
<CheckboxButton
style="flex: 1;"
:checked="!!textFormatPainter"
@click="toggleFormatPainter()"
><IconFormatBrush /></CheckboxButton>
</Tooltip>
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="超链接"> <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="超链接">
<Popover placement="bottomRight" trigger="click" v-model:visible="linkPopoverVisible"> <Popover placement="bottomRight" trigger="click" v-model:visible="linkPopoverVisible">
<template #content> <template #content>
@ -278,12 +286,15 @@ import { PPTTextElement } from '@/types/slides'
import emitter, { EmitterEvents, RichTextAction } from '@/utils/emitter' import emitter, { EmitterEvents, RichTextAction } from '@/utils/emitter'
import { WEB_FONTS } from '@/configs/font' import { WEB_FONTS } from '@/configs/font'
import useHistorySnapshot from '@/hooks/useHistorySnapshot' import useHistorySnapshot from '@/hooks/useHistorySnapshot'
import useTextFormatPainter from '@/hooks/useTextFormatPainter'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import ElementOpacity from '../common/ElementOpacity.vue' import ElementOpacity from '../common/ElementOpacity.vue'
import ElementOutline from '../common/ElementOutline.vue' import ElementOutline from '../common/ElementOutline.vue'
import ElementShadow from '../common/ElementShadow.vue' import ElementShadow from '../common/ElementShadow.vue'
import ColorButton from '../common/ColorButton.vue' import ColorButton from '../common/ColorButton.vue'
import TextColorButton from '../common/TextColorButton.vue'
// BUG // BUG
// //
@ -360,10 +371,12 @@ const presetStyles = [
}, },
] ]
const mainStore = useMainStore()
const slidesStore = useSlidesStore() const slidesStore = useSlidesStore()
const { handleElement, handleElementId, richTextAttrs, availableFonts } = storeToRefs(useMainStore()) const { handleElement, handleElementId, richTextAttrs, availableFonts, textFormatPainter } = storeToRefs(mainStore)
const { addHistorySnapshot } = useHistorySnapshot() const { addHistorySnapshot } = useHistorySnapshot()
const { toggleFormatPainter } = useTextFormatPainter()
const updateElement = (props: Partial<PPTTextElement>) => { const updateElement = (props: Partial<PPTTextElement>) => {
slidesStore.updateElement({ id: handleElementId.value, props }) slidesStore.updateElement({ id: handleElementId.value, props })
@ -491,18 +504,6 @@ const updateLink = (link?: string) => {
margin-top: -1px; margin-top: -1px;
} }
} }
.text-color-btn {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 0;
}
.text-color-block {
width: 16px;
height: 3px;
margin-top: 1px;
}
.font-size-btn { .font-size-btn {
padding: 0; padding: 0;
} }

View File

@ -0,0 +1,38 @@
<template>
<Button class="text-color-btn">
<slot></slot>
<div class="text-color-block">
<div class="text-color-block-content" :style="{ backgroundColor: color }"></div>
</div>
</Button>
</template>
<script lang="ts" setup>
defineProps({
color: {
type: String,
required: true,
},
})
</script>
<style lang="scss" scoped>
.text-color-btn {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 0;
}
.text-color-block {
width: 17px;
height: 4px;
margin-top: 1px;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAACdJREFUGFdjfPbs2X8GBgYGSUlJEMXAiCHw//9/sIrnz59DVKALAADNxxVfaiODNQAAAABJRU5ErkJggg==);
.text-color-block-content {
width: 100%;
height: 100%;
}
}
</style>

View File

@ -1,6 +1,7 @@
<template> <template>
<div <div
class="prosemirror-editor" class="prosemirror-editor"
:class="{ 'format-painter': textFormatPainter }"
ref="editorViewRef" ref="editorViewRef"
></div> ></div>
</template> </template>
@ -14,7 +15,7 @@ import { EditorView } from 'prosemirror-view'
import { toggleMark, wrapIn } from 'prosemirror-commands' import { toggleMark, wrapIn } from 'prosemirror-commands'
import { initProsemirrorEditor, createDocument } from '@/utils/prosemirror' import { initProsemirrorEditor, createDocument } from '@/utils/prosemirror'
import { findNodesWithSameMark, getTextAttrs, autoSelectAll, addMark, markActive, getFontsize } from '@/utils/prosemirror/utils' 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 { alignmentCommand } from '@/utils/prosemirror/commands/setTextAlign'
import { indentCommand } from '@/utils/prosemirror/commands/setTextIndent' import { indentCommand } from '@/utils/prosemirror/commands/setTextIndent'
import { toggleList } from '@/utils/prosemirror/commands/toggleList' import { toggleList } from '@/utils/prosemirror/commands/toggleList'
@ -53,7 +54,7 @@ const emit = defineEmits<{
}>() }>()
const mainStore = useMainStore() const mainStore = useMainStore()
const { handleElementId } = storeToRefs(mainStore) const { handleElementId, textFormatPainter } = storeToRefs(mainStore)
const editorViewRef = ref<HTMLElement>() const editorViewRef = ref<HTMLElement>()
let editorView: EditorView let editorView: EditorView
@ -104,23 +105,6 @@ watch(() => props.editable, () => {
editorView.setProps({ editable: () => 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 // focus
const focus = () => editorView.focus() const focus = () => editorView.focus()
defineExpose({ focus }) defineExpose({ focus })
@ -249,6 +233,38 @@ const execCommand = ({ target, action }: RichTextCommand) => {
handleClick() 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) emitter.on(EmitterEvents.RICH_TEXT_COMMAND, execCommand)
onUnmounted(() => { onUnmounted(() => {
emitter.off(EmitterEvents.RICH_TEXT_COMMAND, execCommand) emitter.off(EmitterEvents.RICH_TEXT_COMMAND, execCommand)
@ -258,5 +274,9 @@ onUnmounted(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.prosemirror-editor { .prosemirror-editor {
cursor: text; cursor: text;
&.format-painter {
cursor: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAVCAYAAACzK0UYAAAABHNCSVQICAgIfAhkiAAAAVRJREFUSInt1DFuwjAYBeCXUrFavgBN9yB6AKR6Bi7AVLrlBpFYgAUpp2i37AysVDIXcCIuwJRMEEYk9LrQDlVQ7EiVOvSt/v1/tmUbeZ7TGMPL5WLgEJLzNE2ptabWmsfjkTeLjTGUUvJ8Pjsjo9GIUkpKKam1voncuTRumn/EKfd1BSQnAF4qhvyK2k1VD88YQ6UUiqJI2+12r2LiPI7j2Xa7rV9yRZbLpRWiAKhGwjW1x3XN828jD9PpVK3X60bAarWy20lZltjv940QwO4KPzbu7oCgLMu/g3Q6ncZI73Q6WSFhGDZGnrIss0LG4zGEEG4ISZUkiW8DDAYDCCEQBIEbAmAWx7GNgSiKAOB1OBzaIyQnSZIom/cRRRG63e7C87z3MAw/fu7Gy/OcRVEgCIK01Wp9/10k37Ism9TdLCHEFzC/zvMPh8Nmt9v5ANDv9/EJD8ykxYswZDkAAAAASUVORK5CYII=) 1 10, default !important;
}
} }
</style> </style>