This commit is contained in:
pipipi-pikachu 2021-01-05 22:28:29 +08:00
parent f2661b58d4
commit 6b67e87699
7 changed files with 264 additions and 81 deletions

View File

@ -74,4 +74,42 @@ export const getAttrValueInSelection = (view: EditorView, attr: string) => {
return keepChecking return keepChecking
}) })
return value return value
} }
export const getTextAttrs = (view: EditorView) => {
const isBold = isActiveMark(view, 'strong')
const isEm = isActiveMark(view, 'em')
const isUnderline = isActiveMark(view, 'underline')
const isStrikethrough = isActiveMark(view, 'strikethrough')
const isSuperscript = isActiveMark(view, 'superscript')
const isSubscript = isActiveMark(view, 'subscript')
const isCode = isActiveMark(view, 'code')
const color = getAttrValue(view, 'forecolor', 'color') || '#000'
const backcolor = getAttrValue(view, 'backcolor', 'backcolor') || '#000'
const fontsize = getAttrValue(view, 'fontsize', 'fontsize') || '12px'
const fontname = getAttrValue(view, 'fontname', 'fontname') || '微软雅黑'
const align = getAttrValueInSelection(view, 'align')
const isBulletList = isActiveOfParentNodeType('bullet_list', view.state)
const isOrderedList = isActiveOfParentNodeType('ordered_list', view.state)
const isBlockquote = isActiveOfParentNodeType('blockquote', view.state)
return {
bold: isBold,
em: isEm,
underline: isUnderline,
strikethrough: isStrikethrough,
superscript: isSuperscript,
subscript: isSubscript,
code: isCode,
color: color,
backcolor: backcolor,
fontsize: fontsize,
fontname: fontname,
align: align,
bulletList: isBulletList,
orderedList: isOrderedList,
blockquote: isBlockquote,
}
}
export type TextAttrs = ReturnType<typeof getTextAttrs>

View File

@ -1,6 +1,8 @@
import mitt, { Emitter } from 'mitt' import mitt, { Emitter } from 'mitt'
export enum EMITTER_EVENTS {} export enum EmitterEvents {
UPDATE_TEXT_STATE = 'UPDATE_TEXT_STATE',
}
const emitter: Emitter = mitt() const emitter: Emitter = mitt()

View File

@ -6,16 +6,18 @@
<div class="animation-pool"> <div class="animation-pool">
<div class="pool-type" v-for="type in animations" :key="type.name"> <div class="pool-type" v-for="type in animations" :key="type.name">
<div class="type-title">{{type.name}}</div> <div class="type-title">{{type.name}}</div>
<div class="pool-item" v-for="item in type.children" :key="item.name"> <div class="pool-item-wrapper">
<div <div
class="pool-item"
v-for="item in type.children" :key="item.name"
:class="[ :class="[
'box',
'animate__animated', 'animate__animated',
hoverPreviewAnimation === item.value && `animate__${item.value}`, hoverPreviewAnimation === item.value && `animate__${item.value}`,
]" ]"
@mouseover="hoverPreviewAnimation = item.value" @mouseover="hoverPreviewAnimation = item.value"
></div> >
<div class="label">{{item.name}}</div> {{item.name}}
</div>
</div> </div>
</div> </div>
</div> </div>
@ -130,9 +132,6 @@ export default defineComponent({
margin-right: -12px; margin-right: -12px;
padding-right: 12px; padding-right: 12px;
} }
.pool-type {
@include grid-layout-wrapper();
}
.type-title { .type-title {
width: 100%; width: 100%;
font-size: 13px; font-size: 13px;
@ -141,25 +140,18 @@ export default defineComponent({
background-color: #eee; background-color: #eee;
padding-left: 10px; padding-left: 10px;
} }
.pool-item-wrapper {
@include grid-layout-wrapper();
}
.pool-item { .pool-item {
@include grid-layout-item(4, 24%); @include grid-layout-item(4, 24%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-bottom: 10px; margin-bottom: 10px;
height: 40px;
line-height: 40px;
text-align: center;
background-color: #f5f5f5;
cursor: pointer; cursor: pointer;
.box {
width: 30px;
height: 30px;
background-color: #eee;
margin-bottom: 5px;
}
.label {
text-align: center;
}
} }
.sequence-item { .sequence-item {

View File

@ -3,88 +3,171 @@
<InputGroup compact class="row"> <InputGroup compact class="row">
<Select <Select
style="flex: 3;" style="flex: 3;"
:value="richTextAttrs.fontname"
> >
<SelectOption value="jack">Jack</SelectOption> <SelectOption v-for="font in availableFonts" :key="font.en" :value="font.en">
<SelectOption value="lucy">Lucy</SelectOption> <span :style="{ fontFamily: font.en }">{{font.zh}}</span>
<SelectOption value="disabled">Disabled</SelectOption> </SelectOption>
<SelectOption value="Yiminghe">yiminghe</SelectOption>
</Select> </Select>
<Select <Select
style="flex: 2;" style="flex: 2;"
:value="richTextAttrs.fontsize"
> >
<SelectOption value="jack">Jack</SelectOption> <SelectOption v-for="fontsize in fontSizeOptions" :key="fontsize" :value="fontsize">
<SelectOption value="lucy">Lucy</SelectOption> {{fontsize}}
<SelectOption value="disabled">Disabled</SelectOption> </SelectOption>
<SelectOption value="Yiminghe">yiminghe</SelectOption>
</Select> </Select>
</InputGroup> </InputGroup>
<ButtonGroup class="row"> <ButtonGroup class="row">
<Button style="flex: 1;"><FontColorsOutlined /></Button> <Popover trigger="click">
<Button style="flex: 1;"><HighlightOutlined /></Button> <template #content>
<Button style="flex: 1;"><BgColorsOutlined /></Button> <ColorPicker v-model="richTextAttrs.color" />
</template>
<Button class="color-btn" style="flex: 1;">
<FontColorsOutlined />
<div class="color-block" :style="{ backgroundColor: richTextAttrs.color }"></div>
</Button>
</Popover>
<Popover trigger="click">
<template #content>
<ColorPicker v-model="richTextAttrs.backcolor" />
</template>
<Button class="color-btn" style="flex: 1;">
<HighlightOutlined />
<div class="color-block" :style="{ backgroundColor: richTextAttrs.backcolor }"></div>
</Button>
</Popover>
<Popover trigger="click">
<template #content>
<ColorPicker v-model="fill" />
</template>
<Button class="color-btn" style="flex: 1;">
<BgColorsOutlined />
<div class="color-block" :style="{ backgroundColor: fill }"></div>
</Button>
</Popover>
</ButtonGroup> </ButtonGroup>
<ButtonGroup class="row"> <ButtonGroup class="row">
<Button style="flex: 1;"><BoldOutlined /></Button> <Button style="flex: 1;" :type="richTextAttrs.bold ? 'primary' : 'default'"><BoldOutlined /></Button>
<Button style="flex: 1;"><ItalicOutlined /></Button> <Button style="flex: 1;" :type="richTextAttrs.em ? 'primary' : 'default'"><ItalicOutlined /></Button>
<Button style="flex: 1;"><UnderlineOutlined /></Button> <Button style="flex: 1;" :type="richTextAttrs.underline ? 'primary' : 'default'"><UnderlineOutlined /></Button>
<Button style="flex: 1;"><StrikethroughOutlined /></Button> <Button style="flex: 1;" :type="richTextAttrs.strikethrough ? 'primary' : 'default'"><StrikethroughOutlined /></Button>
</ButtonGroup>
<ButtonGroup class="row">
<Button style="flex: 1;" :type="richTextAttrs.superscript ? 'primary' : 'default'"></Button>
<Button style="flex: 1;" :type="richTextAttrs.subscript ? 'primary' : 'default'"></Button>
<Button style="flex: 1;" :type="richTextAttrs.code ? 'primary' : 'default'"></Button>
<Button style="flex: 1;" :type="richTextAttrs.blockquote ? 'primary' : 'default'"></Button>
<Button style="flex: 1;"></Button>
</ButtonGroup> </ButtonGroup>
<Divider /> <Divider />
<ButtonGroup class="row"> <ButtonGroup class="row">
<Button style="flex: 1;"><AlignLeftOutlined /></Button> <Button style="flex: 1;" :type="richTextAttrs.align === 'left' || '' ? 'primary' : 'default'"><AlignLeftOutlined /></Button>
<Button style="flex: 1;"><AlignCenterOutlined /></Button> <Button style="flex: 1;" :type="richTextAttrs.align === 'center' ? 'primary' : 'default'"><AlignCenterOutlined /></Button>
<Button style="flex: 1;"><AlignRightOutlined /></Button> <Button style="flex: 1;" :type="richTextAttrs.align === 'right' ? 'primary' : 'default'"><AlignRightOutlined /></Button>
</ButtonGroup> </ButtonGroup>
<ButtonGroup class="row"> <ButtonGroup class="row">
<Button style="flex: 1;"><OrderedListOutlined /></Button> <Button style="flex: 1;" :type="richTextAttrs.bulletList ? 'primary' : 'default'"><UnorderedListOutlined /></Button>
<Button style="flex: 1;"><UnorderedListOutlined /></Button> <Button style="flex: 1;" :type="richTextAttrs.orderedList ? 'primary' : 'default'"><OrderedListOutlined /></Button>
</ButtonGroup> </ButtonGroup>
<Divider /> <Divider />
<div class="row"> <div class="row">
<div style="flex: 2;">行间距</div> <div style="flex: 2;">行间距</div>
<Select style="flex: 3;"> <Select style="flex: 3;" :value="lineHeight">
<template #suffixIcon><ColumnHeightOutlined /></template> <template #suffixIcon><ColumnHeightOutlined /></template>
<SelectOption value="jack">Jack</SelectOption> <SelectOption v-for="item in lineHeightOptions" :key="item" :value="item">{{item}}</SelectOption>
<SelectOption value="lucy">Lucy</SelectOption>
<SelectOption value="disabled">Disabled</SelectOption>
<SelectOption value="Yiminghe">yiminghe</SelectOption>
</Select> </Select>
</div> </div>
<div class="row"> <div class="row">
<div style="flex: 2;">字间距</div> <div style="flex: 2;">字间距</div>
<Select style="flex: 3;"> <Select style="flex: 3;" :value="wordSpace">
<template #suffixIcon><ColumnWidthOutlined /></template> <template #suffixIcon><ColumnWidthOutlined /></template>
<SelectOption value="jack">Jack</SelectOption> <SelectOption v-for="item in wordSpaceOptions" :key="item" :value="item">{{item}}</SelectOption>
<SelectOption value="lucy">Lucy</SelectOption>
<SelectOption value="disabled">Disabled</SelectOption>
<SelectOption value="Yiminghe">yiminghe</SelectOption>
</Select> </Select>
</div> </div>
<Divider /> <Divider />
<ButtonGroup class="row"> <div class="row">
<Button style="flex: 1;">边框</Button> <div style="flex: 2;">描边样式</div>
<Button style="flex: 1;">阴影</Button> <Select style="flex: 3;" :value="wordSpace">
</ButtonGroup> <template #suffixIcon><ColumnWidthOutlined /></template>
<SelectOption v-for="item in wordSpaceOptions" :key="item" :value="item">{{item}}</SelectOption>
</Select>
</div>
<div class="row">
<div style="flex: 2;">描边颜色</div>
<Select style="flex: 3;" :value="wordSpace">
<template #suffixIcon><ColumnWidthOutlined /></template>
<SelectOption v-for="item in wordSpaceOptions" :key="item" :value="item">{{item}}</SelectOption>
</Select>
</div>
<div class="row">
<div style="flex: 2;">描边粗细</div>
<Select style="flex: 3;" :value="wordSpace">
<template #suffixIcon><ColumnWidthOutlined /></template>
<SelectOption v-for="item in wordSpaceOptions" :key="item" :value="item">{{item}}</SelectOption>
</Select>
</div>
<Divider />
<div class="row">
<div style="flex: 2;">水平阴影</div>
<Select style="flex: 3;" :value="wordSpace">
<template #suffixIcon><ColumnWidthOutlined /></template>
<SelectOption v-for="item in wordSpaceOptions" :key="item" :value="item">{{item}}</SelectOption>
</Select>
</div>
<div class="row">
<div style="flex: 2;">垂直阴影</div>
<Select style="flex: 3;" :value="wordSpace">
<template #suffixIcon><ColumnWidthOutlined /></template>
<SelectOption v-for="item in wordSpaceOptions" :key="item" :value="item">{{item}}</SelectOption>
</Select>
</div>
<div class="row">
<div style="flex: 2;">模糊距离</div>
<Select style="flex: 3;" :value="wordSpace">
<template #suffixIcon><ColumnWidthOutlined /></template>
<SelectOption v-for="item in wordSpaceOptions" :key="item" :value="item">{{item}}</SelectOption>
</Select>
</div>
<div class="row">
<div style="flex: 2;">阴影颜色</div>
<Select style="flex: 3;" :value="wordSpace">
<template #suffixIcon><ColumnWidthOutlined /></template>
<SelectOption v-for="item in wordSpaceOptions" :key="item" :value="item">{{item}}</SelectOption>
</Select>
</div>
<Divider />
<div class="row"> <div class="row">
<div style="flex: 2;">透明度</div> <div style="flex: 2;">透明度</div>
<Slider style="flex: 3;" /> <Slider :min="0" :max="1" :step="0.1" :value="opacity" style="flex: 3;" />
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue' import { computed, defineComponent, onUnmounted, ref } from 'vue'
import { Select, Input, Button, Divider, Slider } from 'ant-design-vue' import { useStore } from 'vuex'
import { State } from '@/store'
import { PPTElementOutline, PPTElementShadow } from '@/types/slides'
import emitter, { EmitterEvents } from '@/utils/emitter'
import { TextAttrs } from '@/prosemirror/utils'
import ColorPicker from '@/components/ColorPicker/index.vue'
import { Select, Input, Button, Divider, Slider, Popover } from 'ant-design-vue'
import { import {
FontColorsOutlined, FontColorsOutlined,
HighlightOutlined, HighlightOutlined,
@ -105,6 +188,7 @@ import {
export default defineComponent({ export default defineComponent({
name: 'text-style-panel', name: 'text-style-panel',
components: { components: {
ColorPicker,
Select, Select,
SelectOption: Select.Option, SelectOption: Select.Option,
InputGroup: Input.Group, InputGroup: Input.Group,
@ -112,6 +196,7 @@ export default defineComponent({
ButtonGroup: Button.Group, ButtonGroup: Button.Group,
Divider, Divider,
Slider, Slider,
Popover,
FontColorsOutlined, FontColorsOutlined,
HighlightOutlined, HighlightOutlined,
BgColorsOutlined, BgColorsOutlined,
@ -127,6 +212,63 @@ export default defineComponent({
ColumnHeightOutlined, ColumnHeightOutlined,
ColumnWidthOutlined, ColumnWidthOutlined,
}, },
setup() {
const store = useStore<State>()
const fill = ref('#000')
const lineHeight = ref(1.5)
const wordSpace = ref(0)
const opacity = ref(1)
const shadow = ref<PPTElementShadow>()
const outline = ref<PPTElementOutline>()
const richTextAttrs = ref<TextAttrs>({
bold: false,
em: false,
underline: false,
strikethrough: false,
superscript: false,
subscript: false,
code: false,
color: '#000',
backcolor: '#000',
fontsize: '12px',
fontname: '微软雅黑',
align: 'left',
bulletList: false,
orderedList: false,
blockquote: false,
})
const availableFonts = computed(() => store.state.availableFonts)
const fontSizeOptions = [
'12px', '14px', '16px', '18px', '20px', '22px', '24px', '28px', '32px',
'36px', '40px', '44px', '48px', '54px', '60px', '66px', '72px', '80px',
]
const lineHeightOptions = [0.5, 1.0, 1.2, 1.5, 1.8, 2.0, 3.0]
const wordSpaceOptions = [0, 1, 2, 3, 4, 5, 8]
const updateRichTextAttrs = (attr: TextAttrs) => richTextAttrs.value = attr
emitter.on(EmitterEvents.UPDATE_TEXT_STATE, attr => updateRichTextAttrs(attr))
onUnmounted(() => {
emitter.off(EmitterEvents.UPDATE_TEXT_STATE, attr => updateRichTextAttrs(attr))
})
return {
fill,
lineHeight,
wordSpace,
opacity,
shadow,
outline,
richTextAttrs,
availableFonts,
fontSizeOptions,
lineHeightOptions,
wordSpaceOptions,
}
},
}) })
</script> </script>
@ -137,4 +279,15 @@ export default defineComponent({
align-items: center; align-items: center;
margin-bottom: 10px; margin-bottom: 10px;
} }
.color-btn {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.color-block {
width: 16px;
height: 3px;
margin-top: 1px;
}
</style> </style>

View File

@ -1,32 +1,13 @@
<template> <template>
<div class="slide-style-panel"> <div class="slide-style-panel">
<Popover trigger="click"> slide-style-panel
<template #content>
<ColorPicker v-model="color" />
</template>
<button>Hover me</button>
</Popover>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref } from 'vue' import { defineComponent, ref } from 'vue'
import ColorPicker from '@/components/ColorPicker/index.vue'
import { Popover } from 'ant-design-vue'
export default defineComponent({ export default defineComponent({
name: 'slide-style-panel', name: 'slide-style-panel',
components: {
ColorPicker,
Popover,
},
setup() {
const color = ref('#888')
return {
color,
}
},
}) })
</script> </script>

View File

@ -14,6 +14,8 @@
backgroundColor: elementInfo.fill, backgroundColor: elementInfo.fill,
opacity: elementInfo.opacity, opacity: elementInfo.opacity,
textShadow: shadowStyle, textShadow: shadowStyle,
lineHeight: elementInfo.lineHeight,
letterSpacing: (elementInfo.wordSpace || 0) + 'px',
}" }"
> >
<ElementOutline <ElementOutline

View File

@ -17,6 +17,8 @@
backgroundColor: elementInfo.fill, backgroundColor: elementInfo.fill,
opacity: elementInfo.opacity, opacity: elementInfo.opacity,
textShadow: shadowStyle, textShadow: shadowStyle,
lineHeight: elementInfo.lineHeight,
letterSpacing: (elementInfo.wordSpace || 0) + 'px',
}" }"
v-contextmenu="contextmenus" v-contextmenu="contextmenus"
> >
@ -43,6 +45,8 @@ import { EditorView } from 'prosemirror-view'
import { PPTTextElement } from '@/types/slides' import { PPTTextElement } from '@/types/slides'
import { ContextmenuItem } from '@/components/Contextmenu/types' import { ContextmenuItem } from '@/components/Contextmenu/types'
import { initProsemirrorEditor } from '@/prosemirror/' import { initProsemirrorEditor } from '@/prosemirror/'
import { getTextAttrs } from '@/prosemirror/utils'
import emitter, { EmitterEvents } from '@/utils/emitter'
import useElementShadow from '@/views/components/element/hooks/useElementShadow' import useElementShadow from '@/views/components/element/hooks/useElementShadow'
import useHistorySnapshot from '@/hooks/useHistorySnapshot' import useHistorySnapshot from '@/hooks/useHistorySnapshot'
@ -113,6 +117,16 @@ export default defineComponent({
addHistorySnapshot() addHistorySnapshot()
}, 500, { trailing: true }) }, 500, { trailing: true })
const handleClick = debounce(function() {
const attr = getTextAttrs(editorView)
emitter.emit(EmitterEvents.UPDATE_TEXT_STATE, attr)
}, 50, { trailing: true })
const handleKeydown = () => {
handleInput()
handleClick()
}
const textContent = computed(() => props.elementInfo.content) const textContent = computed(() => props.elementInfo.content)
watch(textContent, () => { watch(textContent, () => {
if(!editorView) return if(!editorView) return
@ -130,7 +144,8 @@ export default defineComponent({
handleDOMEvents: { handleDOMEvents: {
focus: handleFocus, focus: handleFocus,
blur: handleBlur, blur: handleBlur,
keydown: handleInput, keydown: handleKeydown,
click: handleClick,
}, },
editable: () => editable.value, editable: () => editable.value,
}) })