From 2c22196501238dd30fa7d8722c0e8b83063d113f Mon Sep 17 00:00:00 2001 From: pipipi-pikachu Date: Thu, 13 Jul 2023 21:19:59 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E7=B1=BB=E5=9E=8B=E8=A1=A5?= =?UTF-8?q?=E5=85=85=E3=80=81=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ColorPicker/Checkboard.vue | 5 ++++- src/configs/element.ts | 4 ++-- src/configs/imageClip.ts | 12 +++++++++++- src/hooks/useAlignActiveElement.ts | 6 +++++- src/hooks/useExport.ts | 4 ++-- src/hooks/useSlideTheme.ts | 2 +- src/plugins/directive/clickOutside.ts | 10 +++++++--- src/plugins/directive/contextmenu.ts | 8 ++++++-- src/plugins/icon.ts | 6 +++++- src/types/edit.ts | 2 ++ src/types/slides.ts | 1 + src/utils/element.ts | 10 +++++++--- src/utils/htmlParser/parser.ts | 2 +- src/utils/htmlParser/tags.ts | 6 +++++- src/utils/prosemirror/plugins/keymap.ts | 6 +++++- src/utils/prosemirror/schema/nodes.ts | 10 +++++++--- src/views/Editor/Canvas/hooks/useScaleElement.ts | 2 +- src/views/Editor/ExportDialog/index.vue | 3 ++- .../Toolbar/ElementStylePanel/ImageStylePanel.vue | 2 +- src/views/Editor/Toolbar/common/ElementFilter.vue | 7 ++++--- .../element/ImageElement/ImageOutline/index.vue | 2 +- .../components/element/ImageElement/useFilter.ts | 5 +++-- .../element/LineElement/LinePointMarker.vue | 2 +- src/views/components/element/ProsemirrorEditor.vue | 6 ++++-- 24 files changed, 88 insertions(+), 35 deletions(-) diff --git a/src/components/ColorPicker/Checkboard.vue b/src/components/ColorPicker/Checkboard.vue index 07fbcba2..64d8f213 100644 --- a/src/components/ColorPicker/Checkboard.vue +++ b/src/components/ColorPicker/Checkboard.vue @@ -20,7 +20,10 @@ const props = defineProps({ }, }) -const checkboardCache = {} +interface CheckboardCache { + [key: string]: string | null +} +const checkboardCache: CheckboardCache = {} const renderCheckboard = (white: string, grey: string, size: number) => { const canvas = document.createElement('canvas') diff --git a/src/configs/element.ts b/src/configs/element.ts index 9ec41e47..608a11ab 100644 --- a/src/configs/element.ts +++ b/src/configs/element.ts @@ -1,4 +1,4 @@ -export const ELEMENT_TYPE_ZH = { +export const ELEMENT_TYPE_ZH: { [key: string]: string } = { text: '文本', image: '图片', shape: '形状', @@ -10,7 +10,7 @@ export const ELEMENT_TYPE_ZH = { latex: '公式', } -export const MIN_SIZE = { +export const MIN_SIZE: { [key: string]: number } = { text: 20, image: 20, shape: 15, diff --git a/src/configs/imageClip.ts b/src/configs/imageClip.ts index b0aa94a3..da409d57 100644 --- a/src/configs/imageClip.ts +++ b/src/configs/imageClip.ts @@ -14,7 +14,17 @@ export const enum ClipPaths { STAR = 'star', } -export const CLIPPATHS = { +interface ClipPath { + [key: string]: { + name: string + type: ClipPathTypes + style: string + radius?: string + createPath?: (width: number, height: number) => string + } +} + +export const CLIPPATHS: ClipPath = { rect: { name: '矩形', type: ClipPathTypes.RECT, diff --git a/src/hooks/useAlignActiveElement.ts b/src/hooks/useAlignActiveElement.ts index db9e6da4..83ad98eb 100644 --- a/src/hooks/useAlignActiveElement.ts +++ b/src/hooks/useAlignActiveElement.ts @@ -5,6 +5,10 @@ import { ElementAlignCommands } from '@/types/edit' import { getElementListRange, getRectRotatedOffset } from '@/utils/element' import useHistorySnapshot from './useHistorySnapshot' +interface RangeMap { + [id: string]: ReturnType +} + export default () => { const slidesStore = useSlidesStore() const { activeElementIdList, activeElementList } = storeToRefs(useMainStore()) @@ -21,7 +25,7 @@ export default () => { const elementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements)) // 如果所选择的元素为组合元素的成员,需要计算该组合的整体范围 - const groupElementRangeMap = {} + const groupElementRangeMap: RangeMap = {} for (const activeElement of activeElementList.value) { if (activeElement.groupId && !groupElementRangeMap[activeElement.groupId]) { const groupElements = activeElementList.value.filter(item => item.groupId === activeElement.groupId) diff --git a/src/hooks/useExport.ts b/src/hooks/useExport.ts index d8daa602..298168c3 100644 --- a/src/hooks/useExport.ts +++ b/src/hooks/useExport.ts @@ -88,7 +88,7 @@ export default () => { let indent = 0 const slices: pptxgen.TextProps[] = [] - const parse = (obj: AST[], baseStyleObj = {}) => { + const parse = (obj: AST[], baseStyleObj: { [key: string]: string } = {}) => { for (const item of obj) { const isBlockTag = 'tagName' in item && ['div', 'li', 'p'].includes(item.tagName) @@ -186,7 +186,7 @@ export default () => { if (styleObj['vertical-align'] === 'super') options.superscript = true if (styleObj['vertical-align'] === 'sub') options.subscript = true } - if (styleObj['text-align']) options.align = styleObj['text-align'] + if (styleObj['text-align']) options.align = styleObj['text-align'] as pptxgen.HAlign if (styleObj['font-weight']) options.bold = styleObj['font-weight'] === 'bold' if (styleObj['font-style']) options.italic = styleObj['font-style'] === 'italic' if (styleObj['font-family']) options.fontFace = styleObj['font-family'] diff --git a/src/hooks/useSlideTheme.ts b/src/hooks/useSlideTheme.ts index 706525bd..9eb0815e 100644 --- a/src/hooks/useSlideTheme.ts +++ b/src/hooks/useSlideTheme.ts @@ -46,7 +46,7 @@ export default () => { // 创建原颜色与新颜色的对应关系表 const createSlideThemeColorMap = (slide: Slide, newColors: string[]): { [key: string]: string } => { const oldColors = getSlideAllColors(slide) - const themeColorMap = {} + const themeColorMap: { [key: string]: string } = {} if (oldColors.length > newColors.length) { const analogous = tinycolor(newColors[0]).analogous(oldColors.length - newColors.length + 10) diff --git a/src/plugins/directive/clickOutside.ts b/src/plugins/directive/clickOutside.ts index a5bebb4a..f0efa4bd 100644 --- a/src/plugins/directive/clickOutside.ts +++ b/src/plugins/directive/clickOutside.ts @@ -2,6 +2,10 @@ import { Directive, DirectiveBinding } from 'vue' const CTX_CLICK_OUTSIDE_HANDLER = 'CTX_CLICK_OUTSIDE_HANDLER' +interface CustomHTMLElement extends HTMLElement { + [CTX_CLICK_OUTSIDE_HANDLER]?: (event: MouseEvent) => void +} + const clickListener = (el: HTMLElement, event: MouseEvent, binding: DirectiveBinding) => { const handler = binding.value @@ -13,14 +17,14 @@ const clickListener = (el: HTMLElement, event: MouseEvent, binding: DirectiveBin } const ClickOutsideDirective: Directive = { - mounted(el: HTMLElement, binding) { + mounted(el: CustomHTMLElement, binding) { el[CTX_CLICK_OUTSIDE_HANDLER] = (event: MouseEvent) => clickListener(el, event, binding) setTimeout(() => { - document.addEventListener('click', el[CTX_CLICK_OUTSIDE_HANDLER]) + document.addEventListener('click', el[CTX_CLICK_OUTSIDE_HANDLER]!) }, 0) }, - unmounted(el: HTMLElement) { + unmounted(el: CustomHTMLElement) { if (el[CTX_CLICK_OUTSIDE_HANDLER]) { document.removeEventListener('click', el[CTX_CLICK_OUTSIDE_HANDLER]) delete el[CTX_CLICK_OUTSIDE_HANDLER] diff --git a/src/plugins/directive/contextmenu.ts b/src/plugins/directive/contextmenu.ts index 21457b97..43e2f182 100644 --- a/src/plugins/directive/contextmenu.ts +++ b/src/plugins/directive/contextmenu.ts @@ -3,6 +3,10 @@ import ContextmenuComponent from '@/components/Contextmenu/index.vue' const CTX_CONTEXTMENU_HANDLER = 'CTX_CONTEXTMENU_HANDLER' +interface CustomHTMLElement extends HTMLElement { + [CTX_CONTEXTMENU_HANDLER]?: (event: MouseEvent) => void +} + const contextmenuListener = (el: HTMLElement, event: MouseEvent, binding: DirectiveBinding) => { event.stopPropagation() event.preventDefault() @@ -44,12 +48,12 @@ const contextmenuListener = (el: HTMLElement, event: MouseEvent, binding: Direct } const ContextmenuDirective: Directive = { - mounted(el: HTMLElement, binding) { + mounted(el: CustomHTMLElement, binding) { el[CTX_CONTEXTMENU_HANDLER] = (event: MouseEvent) => contextmenuListener(el, event, binding) el.addEventListener('contextmenu', el[CTX_CONTEXTMENU_HANDLER]) }, - unmounted(el: HTMLElement) { + unmounted(el: CustomHTMLElement) { if (el && el[CTX_CONTEXTMENU_HANDLER]) { el.removeEventListener('contextmenu', el[CTX_CONTEXTMENU_HANDLER]) delete el[CTX_CONTEXTMENU_HANDLER] diff --git a/src/plugins/icon.ts b/src/plugins/icon.ts index a123a83b..02a13406 100644 --- a/src/plugins/icon.ts +++ b/src/plugins/icon.ts @@ -114,7 +114,11 @@ import { StopwatchStart, } from '@icon-park/vue-next' -export const icons = { +interface Icons { + [key: string]: typeof PlayOne +} + +export const icons: Icons = { IconPlayOne: PlayOne, IconFullScreenPlay: FullScreenPlay, IconLock: Lock, diff --git a/src/types/edit.ts b/src/types/edit.ts index 464ad2f5..d6509ff4 100644 --- a/src/types/edit.ts +++ b/src/types/edit.ts @@ -92,6 +92,8 @@ export interface CreatingLineElement { } export type CreatingElement = CreatingTextElement | CreatingShapeElement | CreatingLineElement +export type TextFormatPainterKeys = 'bold' | 'em' | 'underline' | 'strikethrough' | 'color' | 'backcolor' | 'fontsize' | 'fontname' | 'align' + export interface TextFormatPainter { bold?: boolean em?: boolean diff --git a/src/types/slides.ts b/src/types/slides.ts index b786826b..b3fe0a85 100644 --- a/src/types/slides.ts +++ b/src/types/slides.ts @@ -194,6 +194,7 @@ export interface ImageOrShapeFlip { * * 'opacity'?: 不透明度,默认100(%) */ +export type ImageElementFilterKeys = 'blur' | 'brightness' | 'contrast' | 'grayscale' | 'saturate' | 'hue-rotate' | 'opacity' export interface ImageElementFilters { 'blur'?: string 'brightness'?: string diff --git a/src/utils/element.ts b/src/utils/element.ts index 5937b19d..bf41dae9 100644 --- a/src/utils/element.ts +++ b/src/utils/element.ts @@ -10,6 +10,10 @@ interface RotatedElementData { rotate: number } +interface IdMap { + [id: string]: string +} + /** * 计算元素在画布中的矩形范围旋转后的新位置范围 * @param element 元素的位置大小和旋转角度信息 @@ -158,7 +162,7 @@ export const uniqAlignLines = (lines: AlignLine[]) => { * @param slides 页面列表 */ export const createSlideIdMap = (slides: Slide[]) => { - const slideIdMap = {} + const slideIdMap: IdMap = {} for (const slide of slides) { slideIdMap[slide.id] = nanoid(10) } @@ -172,8 +176,8 @@ export const createSlideIdMap = (slides: Slide[]) => { * @param elements 元素列表数据 */ export const createElementIdMap = (elements: PPTElement[]) => { - const groupIdMap = {} - const elIdMap = {} + const groupIdMap: IdMap = {} + const elIdMap: IdMap = {} for (const element of elements) { const groupId = element.groupId if (groupId && !groupIdMap[groupId]) { diff --git a/src/utils/htmlParser/parser.ts b/src/utils/htmlParser/parser.ts index f0a721f1..4b939060 100644 --- a/src/utils/htmlParser/parser.ts +++ b/src/utils/htmlParser/parser.ts @@ -26,7 +26,7 @@ export const hasTerminalParent = (tagName: string, stack: StackItem[]) => { while (currentIndex >= 0) { const parentTagName = stack[currentIndex].tagName if (parentTagName === tagName) break - if (tagParents.includes(parentTagName)) return true + if (parentTagName && tagParents.includes(parentTagName)) return true currentIndex-- } } diff --git a/src/utils/htmlParser/tags.ts b/src/utils/htmlParser/tags.ts index d0bff44a..9320250d 100644 --- a/src/utils/htmlParser/tags.ts +++ b/src/utils/htmlParser/tags.ts @@ -2,7 +2,11 @@ export const childlessTags = ['style', 'script', 'template'] export const closingTags = ['html', 'head', 'body', 'p', 'dt', 'dd', 'li', 'option', 'thead', 'th', 'tbody', 'tr', 'td', 'tfoot', 'colgroup'] -export const closingTagAncestorBreakers = { +interface ClosingTagAncestorBreakers { + [key: string]: string[] +} + +export const closingTagAncestorBreakers: ClosingTagAncestorBreakers = { li: ['ul', 'ol', 'menu'], dt: ['dl'], dd: ['dl'], diff --git a/src/utils/prosemirror/plugins/keymap.ts b/src/utils/prosemirror/plugins/keymap.ts index fd493ba5..aad26ffd 100644 --- a/src/utils/prosemirror/plugins/keymap.ts +++ b/src/utils/prosemirror/plugins/keymap.ts @@ -15,8 +15,12 @@ import { splitBlockKeepMarks, } from 'prosemirror-commands' +interface Keys { + [key: string]: Command +} + export const buildKeymap = (schema: Schema) => { - const keys = {} + const keys: Keys = {} const bind = (key: string, cmd: Command) => keys[key] = cmd bind('Alt-ArrowUp', joinUp) diff --git a/src/utils/prosemirror/schema/nodes.ts b/src/utils/prosemirror/schema/nodes.ts index 1b8eb3ec..4a0c3e9f 100644 --- a/src/utils/prosemirror/schema/nodes.ts +++ b/src/utils/prosemirror/schema/nodes.ts @@ -2,6 +2,10 @@ import { nodes } from 'prosemirror-schema-basic' import { Node, NodeSpec } from 'prosemirror-model' import { listItem as _listItem } from 'prosemirror-schema-list' +interface Attr { + [key: string]: number | string +} + const orderedList: NodeSpec = { attrs: { order: { @@ -18,7 +22,7 @@ const orderedList: NodeSpec = { tag: 'ol', getAttrs: dom => { const order = ((dom as HTMLElement).hasAttribute('start') ? (dom as HTMLElement).getAttribute('start') : 1) || 1 - const attr = { order: +order } + const attr: Attr = { order: +order } const { listStyleType } = (dom as HTMLElement).style if (listStyleType) attr['listStyleType'] = listStyleType @@ -32,7 +36,7 @@ const orderedList: NodeSpec = { let style = '' if (listStyleType) style += `list-style-type: ${listStyleType};` - const attr = { style } + const attr: Attr = { style } if (order !== 1) attr['start'] = order @@ -111,7 +115,7 @@ const paragraph: NodeSpec = { let style = '' if (align && align !== 'left') style += `text-align: ${align};` - const attr = { style } + const attr: Attr = { style } if (indent) attr['data-indent'] = indent return ['p', attr, 0] diff --git a/src/views/Editor/Canvas/hooks/useScaleElement.ts b/src/views/Editor/Canvas/hooks/useScaleElement.ts index 25836e74..d5b3df80 100644 --- a/src/views/Editor/Canvas/hooks/useScaleElement.ts +++ b/src/views/Editor/Canvas/hooks/useScaleElement.ts @@ -79,7 +79,7 @@ const getRotateElementPoints = (element: RotateElementData, angle: number) => { * @param direction 当前操作的缩放点 * @param points 旋转后的元素八个缩放点的位置 */ -const getOppositePoint = (direction: string, points: ReturnType): { left: number; top: number } => { +const getOppositePoint = (direction: OperateResizeHandlers, points: ReturnType): { left: number; top: number } => { const oppositeMap = { [OperateResizeHandlers.RIGHT_BOTTOM]: points.leftTopPoint, [OperateResizeHandlers.LEFT_BOTTOM]: points.rightTopPoint, diff --git a/src/views/Editor/ExportDialog/index.vue b/src/views/Editor/ExportDialog/index.vue index 2bb32718..6c7e18f7 100644 --- a/src/views/Editor/ExportDialog/index.vue +++ b/src/views/Editor/ExportDialog/index.vue @@ -53,7 +53,8 @@ const currentDialogComponent = computed(() => { 'pptx': ExportPPTX, 'pptist': ExportSpecificFile, } - return dialogMap[dialogForExport.value] || null + if (dialogForExport.value) return dialogMap[dialogForExport.value] || null + return null }) diff --git a/src/views/Editor/Toolbar/ElementStylePanel/ImageStylePanel.vue b/src/views/Editor/Toolbar/ElementStylePanel/ImageStylePanel.vue index 90ad364a..b69efbb4 100644 --- a/src/views/Editor/Toolbar/ElementStylePanel/ImageStylePanel.vue +++ b/src/views/Editor/Toolbar/ElementStylePanel/ImageStylePanel.vue @@ -18,7 +18,7 @@ class="shape-clip-item" v-for="(item, key) in shapeClipPathOptions" :key="key" - @click="presetImageClip(key)" + @click="presetImageClip(key as string)" >
diff --git a/src/views/Editor/Toolbar/common/ElementFilter.vue b/src/views/Editor/Toolbar/common/ElementFilter.vue index afb2dc1d..4a108ebb 100644 --- a/src/views/Editor/Toolbar/common/ElementFilter.vue +++ b/src/views/Editor/Toolbar/common/ElementFilter.vue @@ -29,14 +29,14 @@ import { ref, watch } from 'vue' import { storeToRefs } from 'pinia' import { useMainStore, useSlidesStore } from '@/store' -import { PPTImageElement } from '@/types/slides' +import { ImageElementFilterKeys, PPTImageElement } from '@/types/slides' import useHistorySnapshot from '@/hooks/useHistorySnapshot' import { Slider, Switch } from 'ant-design-vue' interface FilterOption { label: string - key: string + key: ImageElementFilterKeys default: number value: number unit: string @@ -68,7 +68,8 @@ watch(handleElement, () => { const filters = handleElement.value.filters if (filters) { filterOptions.value = defaultFilters.map(item => { - if (filters[item.key] !== undefined) return { ...item, value: parseInt(filters[item.key]) } + const filterItem = filters[item.key] + if (filterItem) return { ...item, value: parseInt(filterItem) } return item }) hasFilters.value = true diff --git a/src/views/components/element/ImageElement/ImageOutline/index.vue b/src/views/components/element/ImageElement/ImageOutline/index.vue index 2debf54b..f6f98ac2 100644 --- a/src/views/components/element/ImageElement/ImageOutline/index.vue +++ b/src/views/components/element/ImageElement/ImageOutline/index.vue @@ -18,7 +18,7 @@ :width="elementInfo.width" :height="elementInfo.height" :outline="elementInfo.outline" - :createPath="clipShape.createPath" + :createPath="clipShape.createPath!" /> diff --git a/src/views/components/element/ImageElement/useFilter.ts b/src/views/components/element/ImageElement/useFilter.ts index 76596424..d8033304 100644 --- a/src/views/components/element/ImageElement/useFilter.ts +++ b/src/views/components/element/ImageElement/useFilter.ts @@ -1,11 +1,12 @@ import { computed, Ref } from 'vue' -import { ImageElementFilters } from '@/types/slides' +import { ImageElementFilters, ImageElementFilterKeys } from '@/types/slides' export default (filters: Ref) => { const filter = computed(() => { if (!filters.value) return '' let filter = '' - for (const key of Object.keys(filters.value)) { + const keys = Object.keys(filters.value) as ImageElementFilterKeys[] + for (const key of keys) { filter += `${key}(${filters.value[key]}) ` } return filter diff --git a/src/views/components/element/LineElement/LinePointMarker.vue b/src/views/components/element/LineElement/LinePointMarker.vue index 51b9f122..233280b7 100644 --- a/src/views/components/element/LineElement/LinePointMarker.vue +++ b/src/views/components/element/LineElement/LinePointMarker.vue @@ -45,7 +45,7 @@ const pathMap = { dot: 'm0 5a5 5 0 1 0 10 0a5 5 0 1 0 -10 0z', arrow: 'M0,0 L10,5 0,10 Z', } -const rotateMap = { +const rotateMap: { [key: string]: number } = { 'arrow-start': 180, 'arrow-end': 0, } diff --git a/src/views/components/element/ProsemirrorEditor.vue b/src/views/components/element/ProsemirrorEditor.vue index 5f92d3f3..be5fe303 100644 --- a/src/views/components/element/ProsemirrorEditor.vue +++ b/src/views/components/element/ProsemirrorEditor.vue @@ -20,6 +20,7 @@ import emitter, { EmitterEvents, RichTextAction, RichTextCommand } from '@/utils import { alignmentCommand } from '@/utils/prosemirror/commands/setTextAlign' import { indentCommand } from '@/utils/prosemirror/commands/setTextIndent' import { toggleList } from '@/utils/prosemirror/commands/toggleList' +import { TextFormatPainterKeys } from '@/types/edit' const props = defineProps({ elementId: { @@ -242,10 +243,11 @@ const handleMouseup = () => { if (!textFormatPainter.value) return const actions: RichTextAction[] = [{ command: 'clear' }] - for (const key of Object.keys(textFormatPainter.value)) { + for (const key of Object.keys(textFormatPainter.value) as TextFormatPainterKeys[]) { const command = key const value = textFormatPainter.value[key] - if (value) actions.push({ command, value }) + if (value === true) actions.push({ command }) + else if (value) actions.push({ command, value }) } execCommand({ action: actions }) mainStore.setTextFormatPainter(null)