From 23fc06462b4b6c3d08e43d367a3819b977f08efe Mon Sep 17 00:00:00 2001 From: pipipi-pikachu Date: Wed, 6 Dec 2023 23:07:05 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=A4=84=E7=90=86=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E9=A1=B9=E7=9B=AE=E7=AC=A6=E5=8F=B7=E7=9A=84?= =?UTF-8?q?=E5=AD=97=E5=8F=B7=E5=92=8C=E9=A2=9C=E8=89=B2=E9=97=AE=E9=A2=98?= =?UTF-8?q?=EF=BC=88#84=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/styles/prosemirror.scss | 4 +- src/hooks/useExport.ts | 4 +- .../prosemirror/commands/setListStyle.ts | 31 ++++++++++++++ .../prosemirror/commands/setTextIndent.ts | 2 +- src/utils/prosemirror/commands/toggleList.ts | 40 +++++++++++-------- src/utils/prosemirror/schema/nodes.ts | 34 +++++++++++++--- src/utils/prosemirror/utils.ts | 19 ++++++++- src/views/Editor/Remark/Editor.vue | 4 +- .../components/element/ProsemirrorEditor.vue | 23 +++++++++-- 9 files changed, 127 insertions(+), 34 deletions(-) create mode 100644 src/utils/prosemirror/commands/setListStyle.ts diff --git a/src/assets/styles/prosemirror.scss b/src/assets/styles/prosemirror.scss index 1f7e8a4f..6bc1d3c1 100644 --- a/src/assets/styles/prosemirror.scss +++ b/src/assets/styles/prosemirror.scss @@ -23,7 +23,7 @@ ul { list-style-type: disc; - padding-inline-start: 20px; + padding-inline-start: 1.25em; li { list-style-type: inherit; @@ -33,7 +33,7 @@ ol { list-style-type: decimal; - padding-inline-start: 20px; + padding-inline-start: 1.25em; li { list-style-type: inherit; diff --git a/src/hooks/useExport.ts b/src/hooks/useExport.ts index 42796335..5f6c8b03 100644 --- a/src/hooks/useExport.ts +++ b/src/hooks/useExport.ts @@ -193,12 +193,12 @@ export default () => { if (styleObj['href']) options.hyperlink = { url: styleObj['href'] } if (bulletFlag && styleObj['list-type'] === 'ol') { - options.bullet = { type: 'number', indent: 20 * PT_PX_RATIO } + options.bullet = { type: 'number', indent: (options.fontSize || 20) * 1.25 } options.paraSpaceBefore = 0.1 bulletFlag = false } if (bulletFlag && styleObj['list-type'] === 'ul') { - options.bullet = { indent: 20 * PT_PX_RATIO } + options.bullet = { indent: (options.fontSize || 20) * 1.25 } options.paraSpaceBefore = 0.1 bulletFlag = false } diff --git a/src/utils/prosemirror/commands/setListStyle.ts b/src/utils/prosemirror/commands/setListStyle.ts new file mode 100644 index 00000000..58d6fdf2 --- /dev/null +++ b/src/utils/prosemirror/commands/setListStyle.ts @@ -0,0 +1,31 @@ +import type { EditorView } from 'prosemirror-view' +import { isList } from '../utils' + +interface Style { + [key: string]: string +} + +export const setListStyle = (view: EditorView, style: Style | Style[]) => { + const { state } = view + const { schema, selection } = state + const tr = state.tr.setSelection(selection) + + const { doc } = tr + if (!doc) return tr + + const { from, to } = selection + doc.nodesBetween(from, to, (node, pos) => { + if (isList(node, schema)) { + if (from - 3 <= pos && to + 3 >= pos + node.nodeSize) { + const styles = Array.isArray(style) ? style : [style] + + for (const style of styles) { + tr.setNodeAttribute(pos, style.key, style.value) + } + } + } + return false + }) + + view.dispatch(tr) +} \ No newline at end of file diff --git a/src/utils/prosemirror/commands/setTextIndent.ts b/src/utils/prosemirror/commands/setTextIndent.ts index 78281880..f87d0ac9 100644 --- a/src/utils/prosemirror/commands/setTextIndent.ts +++ b/src/utils/prosemirror/commands/setTextIndent.ts @@ -1,7 +1,7 @@ import type { Schema } from 'prosemirror-model' import { type Transaction, TextSelection, AllSelection } from 'prosemirror-state' import type { EditorView } from 'prosemirror-view' -import { isList } from './toggleList' +import { isList } from '../utils' type IndentKey = 'indent' | 'textIndent' diff --git a/src/utils/prosemirror/commands/toggleList.ts b/src/utils/prosemirror/commands/toggleList.ts index 58409c5f..4605e751 100644 --- a/src/utils/prosemirror/commands/toggleList.ts +++ b/src/utils/prosemirror/commands/toggleList.ts @@ -1,16 +1,18 @@ import { wrapInList, liftListItem } from 'prosemirror-schema-list' -import type { Schema, Node, NodeType } from 'prosemirror-model' +import type { Node, NodeType } from 'prosemirror-model' import type { Transaction, EditorState } from 'prosemirror-state' -import { findParentNode } from '../utils' +import { findParentNode, isList } from '../utils' -export const isList = (node: Node, schema: Schema) => { - return ( - node.type === schema.nodes.bullet_list || - node.type === schema.nodes.ordered_list - ) +interface Attr { + [key: string]: number | string } -export const toggleList = (listType: NodeType, itemType: NodeType, listStyleType?: string) => { +interface TextStyleAttr { + color?: string + fontsize?: string +} + +export const toggleList = (listType: NodeType, itemType: NodeType, listStyleType: string, textStyleAttr: TextStyleAttr = {}) => { return (state: EditorState, dispatch: (tr: Transaction) => void) => { const { schema, selection } = state const { $from, $to } = selection @@ -27,14 +29,14 @@ export const toggleList = (listType: NodeType, itemType: NodeType, listStyleType if (isList(parentList.node, schema) && listType.validContent(parentList.node.content)) { const { tr } = state - if (listStyleType) { - const nodeAttrs = { - ...parentList.node.attrs, - listStyleType: listStyleType, - } - tr.setNodeMarkup(parentList.pos, listType, nodeAttrs) + + const nodeAttrs: Attr = { + ...parentList.node.attrs, + ...textStyleAttr, } - else tr.setNodeMarkup(parentList.pos, listType) + if (listStyleType) nodeAttrs.listStyleType = listStyleType + + tr.setNodeMarkup(parentList.pos, listType, nodeAttrs) if (dispatch) dispatch(tr) @@ -42,7 +44,11 @@ export const toggleList = (listType: NodeType, itemType: NodeType, listStyleType } } - if (listStyleType) return wrapInList(listType, { listStyleType })(state, dispatch) - return wrapInList(listType)(state, dispatch) + const nodeAttrs: Attr = { + ...textStyleAttr, + } + if (listStyleType) nodeAttrs.listStyleType = listStyleType + + return wrapInList(listType, nodeAttrs)(state, dispatch) } } \ No newline at end of file diff --git a/src/utils/prosemirror/schema/nodes.ts b/src/utils/prosemirror/schema/nodes.ts index 949dc8f2..8cf6a371 100644 --- a/src/utils/prosemirror/schema/nodes.ts +++ b/src/utils/prosemirror/schema/nodes.ts @@ -14,6 +14,12 @@ const orderedList: NodeSpec = { listStyleType: { default: '', }, + fontsize: { + default: '', + }, + color: { + default: '', + }, }, content: 'list_item+', group: 'block', @@ -24,17 +30,21 @@ const orderedList: NodeSpec = { const order = ((dom as HTMLElement).hasAttribute('start') ? (dom as HTMLElement).getAttribute('start') : 1) || 1 const attr: Attr = { order: +order } - const { listStyleType } = (dom as HTMLElement).style + const { listStyleType, fontSize, color } = (dom as HTMLElement).style if (listStyleType) attr['listStyleType'] = listStyleType + if (fontSize) attr['fontsize'] = fontSize + if (color) attr['color'] = color return attr } } ], toDOM: (node: Node) => { - const { order, listStyleType } = node.attrs + const { order, listStyleType, fontsize, color } = node.attrs let style = '' if (listStyleType) style += `list-style-type: ${listStyleType};` + if (fontsize) style += `font-size: ${fontsize};` + if (color) style += `color: ${color};` const attr: Attr = { style } if (order !== 1) attr['start'] = order @@ -49,6 +59,12 @@ const bulletList: NodeSpec = { listStyleType: { default: '', }, + fontsize: { + default: '', + }, + color: { + default: '', + }, }, content: 'list_item+', group: 'block', @@ -56,15 +72,23 @@ const bulletList: NodeSpec = { { tag: 'ul', getAttrs: dom => { - const { listStyleType } = (dom as HTMLElement).style - return listStyleType ? { listStyleType } : {} + const attr: Attr = {} + + const { listStyleType, fontSize, color } = (dom as HTMLElement).style + if (listStyleType) attr['listStyleType'] = listStyleType + if (fontSize) attr['fontsize'] = fontSize + if (color) attr['color'] = color + + return attr } } ], toDOM: (node: Node) => { - const { listStyleType } = node.attrs + const { listStyleType, fontsize, color } = node.attrs let style = '' if (listStyleType) style += `list-style-type: ${listStyleType};` + if (fontsize) style += `font-size: ${fontsize};` + if (color) style += `color: ${color};` return ['ul', { style }, 0] }, diff --git a/src/utils/prosemirror/utils.ts b/src/utils/prosemirror/utils.ts index 927bf47c..d31bff66 100644 --- a/src/utils/prosemirror/utils.ts +++ b/src/utils/prosemirror/utils.ts @@ -1,8 +1,15 @@ -import type { Node, NodeType, ResolvedPos, Mark, MarkType } from 'prosemirror-model' +import type { Node, NodeType, ResolvedPos, Mark, MarkType, Schema } from 'prosemirror-model' import type { EditorState, Selection } from 'prosemirror-state' import type { EditorView } from 'prosemirror-view' import { selectAll } from 'prosemirror-commands' +export const isList = (node: Node, schema: Schema) => { + return ( + node.type === schema.nodes.bullet_list || + node.type === schema.nodes.ordered_list + ) +} + export const autoSelectAll = (view: EditorView) => { const { empty } = view.state.selection if (empty) selectAll(view.state, view.dispatch) @@ -113,12 +120,20 @@ export const isActiveOfParentNodeType = (nodeType: string, state: EditorState) = return !!findParentNodeOfType(node)(state.selection) } +export const getLastTextNode = (node: Node | null): Node | null => { + if (!node) return null + if (node.type.name === 'text') return node + if (!node.lastChild) return null + + return getLastTextNode(node.lastChild) +} + export const getMarkAttrs = (view: EditorView) => { const { selection, doc } = view.state const { from } = selection let node = doc.nodeAt(from) || doc.nodeAt(from - 1) - if (node?.lastChild) node = node.lastChild + node = getLastTextNode(node) return node?.marks || [] } diff --git a/src/views/Editor/Remark/Editor.vue b/src/views/Editor/Remark/Editor.vue index 10b18bb1..a2b3208c 100644 --- a/src/views/Editor/Remark/Editor.vue +++ b/src/views/Editor/Remark/Editor.vue @@ -146,11 +146,11 @@ const execCommand = (command: string, value?: string) => { } else if (command === 'bulletList') { const { bullet_list: bulletList, list_item: listItem } = editorView.state.schema.nodes - toggleList(bulletList, listItem)(editorView.state, editorView.dispatch) + toggleList(bulletList, listItem, '')(editorView.state, editorView.dispatch) } else if (command === 'orderedList') { const { ordered_list: orderedList, list_item: listItem } = editorView.state.schema.nodes - toggleList(orderedList, listItem)(editorView.state, editorView.dispatch) + toggleList(orderedList, listItem, '')(editorView.state, editorView.dispatch) } else if (command === 'clear') { autoSelectAll(editorView) diff --git a/src/views/components/element/ProsemirrorEditor.vue b/src/views/components/element/ProsemirrorEditor.vue index 182f0fd2..51b2c77d 100644 --- a/src/views/components/element/ProsemirrorEditor.vue +++ b/src/views/components/element/ProsemirrorEditor.vue @@ -20,6 +20,7 @@ import emitter, { EmitterEvents, type RichTextAction, type RichTextCommand } fro import { alignmentCommand } from '@/utils/prosemirror/commands/setTextAlign' import { indentCommand, textIndentCommand } from '@/utils/prosemirror/commands/setTextIndent' import { toggleList } from '@/utils/prosemirror/commands/toggleList' +import { setListStyle } from '@/utils/prosemirror/commands/setListStyle' import type { TextFormatPainterKeys } from '@/types/edit' const props = withDefaults(defineProps<{ @@ -42,7 +43,7 @@ const emit = defineEmits<{ }>() const mainStore = useMainStore() -const { handleElementId, textFormatPainter } = storeToRefs(mainStore) +const { handleElementId, textFormatPainter, richTextAttrs } = storeToRefs(mainStore) const editorViewRef = ref() let editorView: EditorView @@ -115,6 +116,7 @@ const execCommand = ({ target, action }: RichTextCommand) => { const mark = editorView.state.schema.marks.fontsize.create({ fontsize: item.value }) autoSelectAll(editorView) addMark(editorView, mark) + setListStyle(editorView, { key: 'fontsize', value: item.value }) } else if (item.command === 'fontsize-add') { const step = item.value ? +item.value : 2 @@ -122,6 +124,7 @@ const execCommand = ({ target, action }: RichTextCommand) => { const fontsize = getFontsize(editorView) + step + 'px' const mark = editorView.state.schema.marks.fontsize.create({ fontsize }) addMark(editorView, mark) + setListStyle(editorView, { key: 'fontsize', value: fontsize }) } else if (item.command === 'fontsize-reduce') { const step = item.value ? +item.value : 2 @@ -130,11 +133,13 @@ const execCommand = ({ target, action }: RichTextCommand) => { if (fontsize < 12) fontsize = 12 const mark = editorView.state.schema.marks.fontsize.create({ fontsize: fontsize + 'px' }) addMark(editorView, mark) + setListStyle(editorView, { key: 'fontsize', value: fontsize + 'px' }) } else if (item.command === 'color' && item.value) { const mark = editorView.state.schema.marks.forecolor.create({ color: item.value }) autoSelectAll(editorView) addMark(editorView, mark) + setListStyle(editorView, { key: 'color', value: item.value }) } else if (item.command === 'backcolor' && item.value) { const mark = editorView.state.schema.marks.backcolor.create({ backcolor: item.value }) @@ -183,17 +188,29 @@ const execCommand = ({ target, action }: RichTextCommand) => { else if (item.command === 'bulletList') { const listStyleType = item.value || '' const { bullet_list: bulletList, list_item: listItem } = editorView.state.schema.nodes - toggleList(bulletList, listItem, listStyleType)(editorView.state, editorView.dispatch) + const textStyle = { + color: richTextAttrs.value.color, + fontsize: richTextAttrs.value.fontsize + } + toggleList(bulletList, listItem, listStyleType, textStyle)(editorView.state, editorView.dispatch) } else if (item.command === 'orderedList') { const listStyleType = item.value || '' const { ordered_list: orderedList, list_item: listItem } = editorView.state.schema.nodes - toggleList(orderedList, listItem, listStyleType)(editorView.state, editorView.dispatch) + const textStyle = { + color: richTextAttrs.value.color, + fontsize: richTextAttrs.value.fontsize + } + toggleList(orderedList, listItem, listStyleType, textStyle)(editorView.state, editorView.dispatch) } else if (item.command === 'clear') { autoSelectAll(editorView) const { $from, $to } = editorView.state.selection editorView.dispatch(editorView.state.tr.removeMark($from.pos, $to.pos)) + setListStyle(editorView, [ + { key: 'fontsize', value: '' }, + { key: 'color', value: '' }, + ]) } else if (item.command === 'link') { const markType = editorView.state.schema.marks.link