fix: 处理文本列表项目符号的字号和颜色问题(#84)

This commit is contained in:
pipipi-pikachu 2023-12-06 23:07:05 +08:00
parent 9b8a742a37
commit 23fc06462b
9 changed files with 127 additions and 34 deletions

View File

@ -23,7 +23,7 @@
ul { ul {
list-style-type: disc; list-style-type: disc;
padding-inline-start: 20px; padding-inline-start: 1.25em;
li { li {
list-style-type: inherit; list-style-type: inherit;
@ -33,7 +33,7 @@
ol { ol {
list-style-type: decimal; list-style-type: decimal;
padding-inline-start: 20px; padding-inline-start: 1.25em;
li { li {
list-style-type: inherit; list-style-type: inherit;

View File

@ -193,12 +193,12 @@ export default () => {
if (styleObj['href']) options.hyperlink = { url: styleObj['href'] } if (styleObj['href']) options.hyperlink = { url: styleObj['href'] }
if (bulletFlag && styleObj['list-type'] === 'ol') { 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 options.paraSpaceBefore = 0.1
bulletFlag = false bulletFlag = false
} }
if (bulletFlag && styleObj['list-type'] === 'ul') { 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 options.paraSpaceBefore = 0.1
bulletFlag = false bulletFlag = false
} }

View File

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

View File

@ -1,7 +1,7 @@
import type { Schema } from 'prosemirror-model' import type { Schema } from 'prosemirror-model'
import { type Transaction, TextSelection, AllSelection } from 'prosemirror-state' import { type Transaction, TextSelection, AllSelection } from 'prosemirror-state'
import type { EditorView } from 'prosemirror-view' import type { EditorView } from 'prosemirror-view'
import { isList } from './toggleList' import { isList } from '../utils'
type IndentKey = 'indent' | 'textIndent' type IndentKey = 'indent' | 'textIndent'

View File

@ -1,16 +1,18 @@
import { wrapInList, liftListItem } from 'prosemirror-schema-list' 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 type { Transaction, EditorState } from 'prosemirror-state'
import { findParentNode } from '../utils' import { findParentNode, isList } from '../utils'
export const isList = (node: Node, schema: Schema) => { interface Attr {
return ( [key: string]: number | string
node.type === schema.nodes.bullet_list ||
node.type === schema.nodes.ordered_list
)
} }
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) => { return (state: EditorState, dispatch: (tr: Transaction) => void) => {
const { schema, selection } = state const { schema, selection } = state
const { $from, $to } = selection 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)) { if (isList(parentList.node, schema) && listType.validContent(parentList.node.content)) {
const { tr } = state const { tr } = state
if (listStyleType) {
const nodeAttrs = { const nodeAttrs: Attr = {
...parentList.node.attrs, ...parentList.node.attrs,
listStyleType: listStyleType, ...textStyleAttr,
} }
if (listStyleType) nodeAttrs.listStyleType = listStyleType
tr.setNodeMarkup(parentList.pos, listType, nodeAttrs) tr.setNodeMarkup(parentList.pos, listType, nodeAttrs)
}
else tr.setNodeMarkup(parentList.pos, listType)
if (dispatch) dispatch(tr) if (dispatch) dispatch(tr)
@ -42,7 +44,11 @@ export const toggleList = (listType: NodeType, itemType: NodeType, listStyleType
} }
} }
if (listStyleType) return wrapInList(listType, { listStyleType })(state, dispatch) const nodeAttrs: Attr = {
return wrapInList(listType)(state, dispatch) ...textStyleAttr,
}
if (listStyleType) nodeAttrs.listStyleType = listStyleType
return wrapInList(listType, nodeAttrs)(state, dispatch)
} }
} }

View File

@ -14,6 +14,12 @@ const orderedList: NodeSpec = {
listStyleType: { listStyleType: {
default: '', default: '',
}, },
fontsize: {
default: '',
},
color: {
default: '',
},
}, },
content: 'list_item+', content: 'list_item+',
group: 'block', group: 'block',
@ -24,17 +30,21 @@ const orderedList: NodeSpec = {
const order = ((dom as HTMLElement).hasAttribute('start') ? (dom as HTMLElement).getAttribute('start') : 1) || 1 const order = ((dom as HTMLElement).hasAttribute('start') ? (dom as HTMLElement).getAttribute('start') : 1) || 1
const attr: Attr = { order: +order } 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 (listStyleType) attr['listStyleType'] = listStyleType
if (fontSize) attr['fontsize'] = fontSize
if (color) attr['color'] = color
return attr return attr
} }
} }
], ],
toDOM: (node: Node) => { toDOM: (node: Node) => {
const { order, listStyleType } = node.attrs const { order, listStyleType, fontsize, color } = node.attrs
let style = '' let style = ''
if (listStyleType) style += `list-style-type: ${listStyleType};` if (listStyleType) style += `list-style-type: ${listStyleType};`
if (fontsize) style += `font-size: ${fontsize};`
if (color) style += `color: ${color};`
const attr: Attr = { style } const attr: Attr = { style }
if (order !== 1) attr['start'] = order if (order !== 1) attr['start'] = order
@ -49,6 +59,12 @@ const bulletList: NodeSpec = {
listStyleType: { listStyleType: {
default: '', default: '',
}, },
fontsize: {
default: '',
},
color: {
default: '',
},
}, },
content: 'list_item+', content: 'list_item+',
group: 'block', group: 'block',
@ -56,15 +72,23 @@ const bulletList: NodeSpec = {
{ {
tag: 'ul', tag: 'ul',
getAttrs: dom => { getAttrs: dom => {
const { listStyleType } = (dom as HTMLElement).style const attr: Attr = {}
return listStyleType ? { listStyleType } : {}
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) => { toDOM: (node: Node) => {
const { listStyleType } = node.attrs const { listStyleType, fontsize, color } = node.attrs
let style = '' let style = ''
if (listStyleType) style += `list-style-type: ${listStyleType};` if (listStyleType) style += `list-style-type: ${listStyleType};`
if (fontsize) style += `font-size: ${fontsize};`
if (color) style += `color: ${color};`
return ['ul', { style }, 0] return ['ul', { style }, 0]
}, },

View File

@ -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 { EditorState, Selection } from 'prosemirror-state'
import type { EditorView } from 'prosemirror-view' import type { EditorView } from 'prosemirror-view'
import { selectAll } from 'prosemirror-commands' 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) => { export const autoSelectAll = (view: EditorView) => {
const { empty } = view.state.selection const { empty } = view.state.selection
if (empty) selectAll(view.state, view.dispatch) if (empty) selectAll(view.state, view.dispatch)
@ -113,12 +120,20 @@ export const isActiveOfParentNodeType = (nodeType: string, state: EditorState) =
return !!findParentNodeOfType(node)(state.selection) 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) => { export const getMarkAttrs = (view: EditorView) => {
const { selection, doc } = view.state const { selection, doc } = view.state
const { from } = selection const { from } = selection
let node = doc.nodeAt(from) || doc.nodeAt(from - 1) let node = doc.nodeAt(from) || doc.nodeAt(from - 1)
if (node?.lastChild) node = node.lastChild node = getLastTextNode(node)
return node?.marks || [] return node?.marks || []
} }

View File

@ -146,11 +146,11 @@ const execCommand = (command: string, value?: string) => {
} }
else if (command === 'bulletList') { else if (command === 'bulletList') {
const { bullet_list: bulletList, list_item: listItem } = editorView.state.schema.nodes 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') { else if (command === 'orderedList') {
const { ordered_list: orderedList, list_item: listItem } = editorView.state.schema.nodes 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') { else if (command === 'clear') {
autoSelectAll(editorView) autoSelectAll(editorView)

View File

@ -20,6 +20,7 @@ import emitter, { EmitterEvents, type RichTextAction, type RichTextCommand } fro
import { alignmentCommand } from '@/utils/prosemirror/commands/setTextAlign' import { alignmentCommand } from '@/utils/prosemirror/commands/setTextAlign'
import { indentCommand, textIndentCommand } from '@/utils/prosemirror/commands/setTextIndent' import { indentCommand, textIndentCommand } from '@/utils/prosemirror/commands/setTextIndent'
import { toggleList } from '@/utils/prosemirror/commands/toggleList' import { toggleList } from '@/utils/prosemirror/commands/toggleList'
import { setListStyle } from '@/utils/prosemirror/commands/setListStyle'
import type { TextFormatPainterKeys } from '@/types/edit' import type { TextFormatPainterKeys } from '@/types/edit'
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
@ -42,7 +43,7 @@ const emit = defineEmits<{
}>() }>()
const mainStore = useMainStore() const mainStore = useMainStore()
const { handleElementId, textFormatPainter } = storeToRefs(mainStore) const { handleElementId, textFormatPainter, richTextAttrs } = storeToRefs(mainStore)
const editorViewRef = ref<HTMLElement>() const editorViewRef = ref<HTMLElement>()
let editorView: EditorView let editorView: EditorView
@ -115,6 +116,7 @@ const execCommand = ({ target, action }: RichTextCommand) => {
const mark = editorView.state.schema.marks.fontsize.create({ fontsize: item.value }) const mark = editorView.state.schema.marks.fontsize.create({ fontsize: item.value })
autoSelectAll(editorView) autoSelectAll(editorView)
addMark(editorView, mark) addMark(editorView, mark)
setListStyle(editorView, { key: 'fontsize', value: item.value })
} }
else if (item.command === 'fontsize-add') { else if (item.command === 'fontsize-add') {
const step = item.value ? +item.value : 2 const step = item.value ? +item.value : 2
@ -122,6 +124,7 @@ const execCommand = ({ target, action }: RichTextCommand) => {
const fontsize = getFontsize(editorView) + step + 'px' const fontsize = getFontsize(editorView) + step + 'px'
const mark = editorView.state.schema.marks.fontsize.create({ fontsize }) const mark = editorView.state.schema.marks.fontsize.create({ fontsize })
addMark(editorView, mark) addMark(editorView, mark)
setListStyle(editorView, { key: 'fontsize', value: fontsize })
} }
else if (item.command === 'fontsize-reduce') { else if (item.command === 'fontsize-reduce') {
const step = item.value ? +item.value : 2 const step = item.value ? +item.value : 2
@ -130,11 +133,13 @@ const execCommand = ({ target, action }: RichTextCommand) => {
if (fontsize < 12) fontsize = 12 if (fontsize < 12) fontsize = 12
const mark = editorView.state.schema.marks.fontsize.create({ fontsize: fontsize + 'px' }) const mark = editorView.state.schema.marks.fontsize.create({ fontsize: fontsize + 'px' })
addMark(editorView, mark) addMark(editorView, mark)
setListStyle(editorView, { key: 'fontsize', value: fontsize + 'px' })
} }
else if (item.command === 'color' && item.value) { else if (item.command === 'color' && item.value) {
const mark = editorView.state.schema.marks.forecolor.create({ color: item.value }) const mark = editorView.state.schema.marks.forecolor.create({ color: item.value })
autoSelectAll(editorView) autoSelectAll(editorView)
addMark(editorView, mark) addMark(editorView, mark)
setListStyle(editorView, { key: 'color', value: item.value })
} }
else if (item.command === 'backcolor' && item.value) { else if (item.command === 'backcolor' && item.value) {
const mark = editorView.state.schema.marks.backcolor.create({ 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') { else if (item.command === 'bulletList') {
const listStyleType = item.value || '' const listStyleType = item.value || ''
const { bullet_list: bulletList, list_item: listItem } = editorView.state.schema.nodes 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') { else if (item.command === 'orderedList') {
const listStyleType = item.value || '' const listStyleType = item.value || ''
const { ordered_list: orderedList, list_item: listItem } = editorView.state.schema.nodes 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') { else if (item.command === 'clear') {
autoSelectAll(editorView) autoSelectAll(editorView)
const { $from, $to } = editorView.state.selection const { $from, $to } = editorView.state.selection
editorView.dispatch(editorView.state.tr.removeMark($from.pos, $to.pos)) editorView.dispatch(editorView.state.tr.removeMark($from.pos, $to.pos))
setListStyle(editorView, [
{ key: 'fontsize', value: '' },
{ key: 'color', value: '' },
])
} }
else if (item.command === 'link') { else if (item.command === 'link') {
const markType = editorView.state.schema.marks.link const markType = editorView.state.schema.marks.link