pipipi-pikachu 5 lat temu
rodzic
commit
942488f47c

+ 9 - 0
src/assets/styles/antd.scss

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

+ 1 - 1
src/prosemirror/commands/setTextAlign.ts

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

+ 1 - 0
src/utils/emitter.ts

@@ -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()

+ 89 - 20
src/views/Editor/Toolbar/ElementStylePanel/TextStylePanel.vue

@@ -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;

+ 2 - 0
src/views/components/element/TextElement/BaseTextElement.vue

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

+ 88 - 3
src/views/components/element/TextElement/index.vue

@@ -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;