This commit is contained in:
pipipi-pikachu 2021-01-07 22:17:46 +08:00
parent 5bf32419bb
commit 942488f47c
6 changed files with 190 additions and 24 deletions

View File

@ -20,3 +20,12 @@
.ant-radio-button-wrapper {
text-align: center;
}
.ant-slider-track {
background-color: $themeColor;
}
.ant-slider-handle {
border-color: $themeColor;
}
.ant-select {
user-select: none;
}

View File

@ -44,7 +44,7 @@ export const setTextAlign = (tr: Transaction, schema: Schema, alignment: string)
let { attrs } = node
if(alignment) attrs = { ...attrs, align: alignment }
else attrs = { ...attrs, align: null }
tr = tr.setNodeMarkup(pos, nodeType, attrs, node.marks);
tr = tr.setNodeMarkup(pos, nodeType, attrs, node.marks)
})
return tr

View File

@ -2,6 +2,7 @@ import mitt, { Emitter } from 'mitt'
export enum EmitterEvents {
UPDATE_TEXT_STATE = 'UPDATE_TEXT_STATE',
EXEC_TEXT_COMMAND = 'EXEC_TEXT_COMMAND',
}
const emitter: Emitter = mitt()

View File

@ -4,6 +4,7 @@
<Select
style="flex: 3;"
:value="richTextAttrs.fontname"
@change="value => emitRichTextCommand('fontname', value)"
>
<SelectOption v-for="font in availableFonts" :key="font.en" :value="font.en">
<span :style="{ fontFamily: font.en }">{{font.zh}}</span>
@ -12,6 +13,7 @@
<Select
style="flex: 2;"
:value="richTextAttrs.fontsize"
@change="value => emitRichTextCommand('fontsize', value)"
>
<SelectOption v-for="fontsize in fontSizeOptions" :key="fontsize" :value="fontsize">
{{fontsize}}
@ -22,7 +24,10 @@
<ButtonGroup class="row">
<Popover trigger="click">
<template #content>
<ColorPicker v-model="richTextAttrs.color" />
<ColorPicker
:modelValue="richTextAttrs.color"
@update:modelValue="value => emitRichTextCommand('color', value)"
/>
</template>
<Button class="text-color-btn" style="flex: 1;">
<FontColorsOutlined />
@ -31,7 +36,10 @@
</Popover>
<Popover trigger="click">
<template #content>
<ColorPicker v-model="richTextAttrs.backcolor" />
<ColorPicker
:modelValue="richTextAttrs.backcolor"
@update:modelValue="value => emitRichTextCommand('backcolor', value)"
/>
</template>
<Button class="text-color-btn" style="flex: 1;">
<HighlightOutlined />
@ -40,7 +48,10 @@
</Popover>
<Popover trigger="click">
<template #content>
<ColorPicker v-model="fill" />
<ColorPicker
:modelValue="fill"
@update:modelValue="value => updateFill(value)"
/>
</template>
<Button class="text-color-btn" style="flex: 1;">
<BgColorsOutlined />
@ -50,31 +61,79 @@
</ButtonGroup>
<CheckboxButtonGroup class="row">
<CheckboxButton style="flex: 1;" :checked="richTextAttrs.bold"><BoldOutlined /></CheckboxButton>
<CheckboxButton style="flex: 1;" :checked="richTextAttrs.em"><ItalicOutlined /></CheckboxButton>
<CheckboxButton style="flex: 1;" :checked="richTextAttrs.underline"><UnderlineOutlined /></CheckboxButton>
<CheckboxButton style="flex: 1;" :checked="richTextAttrs.strikethrough"><StrikethroughOutlined /></CheckboxButton>
<CheckboxButton
style="flex: 1;"
:checked="richTextAttrs.bold"
@click="emitRichTextCommand('bold')"
><BoldOutlined /></CheckboxButton>
<CheckboxButton
style="flex: 1;"
:checked="richTextAttrs.em"
@click="emitRichTextCommand('em')"
><ItalicOutlined /></CheckboxButton>
<CheckboxButton
style="flex: 1;"
:checked="richTextAttrs.underline"
@click="emitRichTextCommand('underline')"
><UnderlineOutlined /></CheckboxButton>
<CheckboxButton
style="flex: 1;"
:checked="richTextAttrs.strikethrough"
@click="emitRichTextCommand('strikethrough')"
><StrikethroughOutlined /></CheckboxButton>
</CheckboxButtonGroup>
<CheckboxButtonGroup class="row">
<CheckboxButton style="flex: 1;" :checked="richTextAttrs.superscript"></CheckboxButton>
<CheckboxButton style="flex: 1;" :checked="richTextAttrs.subscript"></CheckboxButton>
<CheckboxButton style="flex: 1;" :checked="richTextAttrs.code"></CheckboxButton>
<CheckboxButton style="flex: 1;" :checked="richTextAttrs.blockquote"></CheckboxButton>
<CheckboxButton style="flex: 1;"></CheckboxButton>
<CheckboxButton
style="flex: 1;"
:checked="richTextAttrs.superscript"
@click="emitRichTextCommand('superscript')"
></CheckboxButton>
<CheckboxButton
style="flex: 1;"
:checked="richTextAttrs.subscript"
@click="emitRichTextCommand('subscript')"
></CheckboxButton>
<CheckboxButton
style="flex: 1;"
:checked="richTextAttrs.code"
@click="emitRichTextCommand('code')"
></CheckboxButton>
<CheckboxButton
style="flex: 1;"
:checked="richTextAttrs.blockquote"
@click="emitRichTextCommand('blockquote')"
></CheckboxButton>
<CheckboxButton
style="flex: 1;"
@click="emitRichTextCommand('clear')"
></CheckboxButton>
</CheckboxButtonGroup>
<Divider />
<RadioGroup class="row" button-style="solid" :value="richTextAttrs.align">
<RadioGroup
class="row"
button-style="solid"
:value="richTextAttrs.align"
@change="e => emitRichTextCommand('align', e.target.value)"
>
<RadioButton value="left" style="flex: 1;"><AlignLeftOutlined /></RadioButton>
<RadioButton value="center" style="flex: 1;"><AlignCenterOutlined /></RadioButton>
<RadioButton value="right" style="flex: 1;"><AlignRightOutlined /></RadioButton>
</RadioGroup>
<CheckboxButtonGroup class="row">
<CheckboxButton style="flex: 1;" :checked="richTextAttrs.bulletList"><UnorderedListOutlined /></CheckboxButton>
<CheckboxButton style="flex: 1;" :checked="richTextAttrs.orderedList"><OrderedListOutlined /></CheckboxButton>
<CheckboxButton
style="flex: 1;"
:checked="richTextAttrs.bulletList"
@click="emitRichTextCommand('bulletList')"
><UnorderedListOutlined /></CheckboxButton>
<CheckboxButton
style="flex: 1;"
:checked="richTextAttrs.orderedList"
@click="emitRichTextCommand('orderedList')"
><OrderedListOutlined /></CheckboxButton>
</CheckboxButtonGroup>
<Divider />
@ -95,15 +154,10 @@
</div>
<Divider />
<ElementOutline />
<Divider />
<ElementShadow />
<Divider />
<ElementOpacity />
</div>
</template>
@ -222,6 +276,10 @@ export default defineComponent({
emitter.off(EmitterEvents.UPDATE_TEXT_STATE, attr => updateRichTextAttrs(attr))
})
const emitRichTextCommand = (command: string, value?: string) => {
emitter.emit(EmitterEvents.EXEC_TEXT_COMMAND, { command, value })
}
const { addHistorySnapshot } = useHistorySnapshot()
const updateLineHeight = (value: number) => {
@ -236,6 +294,12 @@ export default defineComponent({
addHistorySnapshot()
}
const updateFill = (value: string) => {
const props = { fill: value }
store.commit(MutationTypes.UPDATE_ELEMENT, { id: handleElement.value.id, props })
addHistorySnapshot()
}
return {
fill,
lineHeight,
@ -247,12 +311,17 @@ export default defineComponent({
wordSpaceOptions,
updateLineHeight,
updateWordSpace,
updateFill,
emitRichTextCommand,
}
},
})
</script>
<style lang="scss" scoped>
.text-style-panel {
user-select: none;
}
.row {
width: 100%;
display: flex;

View File

@ -66,6 +66,8 @@ export default defineComponent({
position: relative;
padding: 10px;
line-height: 1.5;
word-break: break-word;
font-family: '微软雅黑';
.text {
position: relative;

View File

@ -51,6 +51,14 @@ import useElementShadow from '@/views/components/element/hooks/useElementShadow'
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
import ElementOutline from '@/views/components/element/ElementOutline.vue'
import { toggleMark, wrapIn } from 'prosemirror-commands'
import { alignmentCommand } from '@/prosemirror/commands/setTextAlign'
import { toggleList } from '@/prosemirror/commands/toggleList'
interface CommandPayload {
command: string;
value?: string;
}
export default defineComponent({
name: 'editable-element-text',
@ -81,7 +89,7 @@ export default defineComponent({
id: props.elementInfo.id,
props: { height: realHeight },
})
}, 500, { trailing: true })
}, 300, { trailing: true })
const updateTextElementHeight = () => {
if(!elementRef.value) return
@ -115,12 +123,12 @@ export default defineComponent({
props: { content: editorView.dom.innerHTML },
})
addHistorySnapshot()
}, 500, { trailing: true })
}, 300, { trailing: true })
const handleClick = debounce(function() {
const attr = getTextAttrs(editorView)
emitter.emit(EmitterEvents.UPDATE_TEXT_STATE, attr)
}, 50, { trailing: true })
}, 30, { trailing: true })
const handleKeydown = () => {
handleInput()
@ -164,6 +172,81 @@ export default defineComponent({
const shadow = computed(() => props.elementInfo.shadow)
const { shadowStyle } = useElementShadow(shadow)
const handleElementId = computed(() => store.state.handleElementId)
const execCommand = (payload: CommandPayload) => {
if(handleElementId.value !== props.elementInfo.id) return
if(payload.command === 'fontname' && payload.value) {
const mark = editorView.state.schema.marks.fontname.create({ fontname: payload.value })
const { $from, $to } = editorView.state.selection
editorView.dispatch(editorView.state.tr.addMark($from.pos, $to.pos, mark))
}
else if(payload.command === 'fontsize' && payload.value) {
const mark = editorView.state.schema.marks.fontsize.create({ fontsize: payload.value })
const { $from, $to } = editorView.state.selection
editorView.dispatch(editorView.state.tr.addMark($from.pos, $to.pos, mark))
}
else if(payload.command === 'color' && payload.value) {
const mark = editorView.state.schema.marks.forecolor.create({ color: payload.value })
const { $from, $to } = editorView.state.selection
editorView.dispatch(editorView.state.tr.addMark($from.pos, $to.pos, mark))
}
else if(payload.command === 'backcolor' && payload.value) {
const mark = editorView.state.schema.marks.backcolor.create({ backcolor: payload.value })
const { $from, $to } = editorView.state.selection
editorView.dispatch(editorView.state.tr.addMark($from.pos, $to.pos, mark))
}
else if(payload.command === 'bold') {
toggleMark(editorView.state.schema.marks.strong)(editorView.state, editorView.dispatch)
}
else if(payload.command === 'em') {
toggleMark(editorView.state.schema.marks.em)(editorView.state, editorView.dispatch)
}
else if(payload.command === 'underline') {
toggleMark(editorView.state.schema.marks.underline)(editorView.state, editorView.dispatch)
}
else if(payload.command === 'strikethrough') {
toggleMark(editorView.state.schema.marks.strikethrough)(editorView.state, editorView.dispatch)
}
else if(payload.command === 'subscript') {
toggleMark(editorView.state.schema.marks.subscript)(editorView.state, editorView.dispatch)
}
else if(payload.command === 'superscript') {
toggleMark(editorView.state.schema.marks.superscript)(editorView.state, editorView.dispatch)
}
else if(payload.command === 'blockquote') {
wrapIn(editorView.state.schema.nodes.blockquote)(editorView.state, editorView.dispatch)
}
else if(payload.command === 'code') {
toggleMark(editorView.state.schema.marks.code)(editorView.state, editorView.dispatch)
}
else if(payload.command === 'align' && payload.value) {
alignmentCommand(editorView, payload.value)
}
else if(payload.command === 'bulletList') {
const { bullet_list: bulletList, list_item: listItem } = editorView.state.schema.nodes
toggleList(bulletList, listItem)(editorView.state, editorView.dispatch)
}
else if(payload.command === 'orderedList') {
const { ordered_list: orderedList, list_item: listItem } = editorView.state.schema.nodes
toggleList(orderedList, listItem)(editorView.state, editorView.dispatch)
}
else if(payload.command === 'clear') {
if(editorView.state.selection.empty) return false
const { $from, $to } = editorView.state.selection
editorView.dispatch(editorView.state.tr.removeMark($from.pos, $to.pos))
}
editorView.focus()
handleInput()
handleClick()
}
emitter.on(EmitterEvents.EXEC_TEXT_COMMAND, payload => execCommand(payload))
onUnmounted(() => {
emitter.off(EmitterEvents.EXEC_TEXT_COMMAND, payload => execCommand(payload))
})
return {
elementRef,
editorViewRef,
@ -188,6 +271,8 @@ export default defineComponent({
position: relative;
padding: 10px;
line-height: 1.5;
word-break: break-word;
font-family: '微软雅黑';
.text {
position: relative;