mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
fix: 处理文本列表项目符号的字号和颜色问题(#84)
This commit is contained in:
parent
9b8a742a37
commit
23fc06462b
@ -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;
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
31
src/utils/prosemirror/commands/setListStyle.ts
Normal file
31
src/utils/prosemirror/commands/setListStyle.ts
Normal 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)
|
||||||
|
}
|
@ -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'
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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]
|
||||||
},
|
},
|
||||||
|
@ -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 || []
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user