feat: 文字项目符号支持多种样式

This commit is contained in:
pipipi-pikachu 2022-11-05 11:38:06 +08:00
parent f4af2405dc
commit 10c6b3daa3
6 changed files with 189 additions and 36 deletions

View File

@ -48,8 +48,7 @@ body {
}
ol,
ul,
li {
ul {
list-style: none;
}

View File

@ -29,7 +29,7 @@
padding-inline-start: 20px;
li {
list-style-type: disc;
list-style-type: inherit;
}
}
@ -38,7 +38,7 @@
padding-inline-start: 20px;
li {
list-style-type: decimal;
list-style-type: inherit;
}
}

View File

@ -10,7 +10,7 @@ export const isList = (node: Node, schema: Schema) => {
)
}
export const toggleList = (listType: NodeType, itemType: NodeType) => {
export const toggleList = (listType: NodeType, itemType: NodeType, listStyleType?: string) => {
return (state: EditorState, dispatch: (tr: Transaction) => void) => {
const { schema, selection } = state
const { $from, $to } = selection
@ -21,13 +21,20 @@ export const toggleList = (listType: NodeType, itemType: NodeType) => {
const parentList = findParentNode((node: Node) => isList(node, schema))(selection)
if (range.depth >= 1 && parentList && range.depth - parentList.depth <= 1) {
if (parentList.node.type === listType) {
if (parentList.node.type === listType && !listStyleType) {
return liftListItem(itemType)(state, dispatch)
}
if (isList(parentList.node, schema) && listType.validContent(parentList.node.content)) {
const { tr } = state
tr.setNodeMarkup(parentList.pos, listType)
if (listStyleType) {
const nodeAttrs = {
...parentList.node.attrs,
listStyleType: listStyleType,
}
tr.setNodeMarkup(parentList.pos, listType, nodeAttrs)
}
else tr.setNodeMarkup(parentList.pos, listType)
if (dispatch) dispatch(tr)
@ -35,6 +42,7 @@ export const toggleList = (listType: NodeType, itemType: NodeType) => {
}
}
if (listStyleType) return wrapInList(listType, { listStyleType })(state, dispatch)
return wrapInList(listType)(state, dispatch)
}
}

View File

@ -1,21 +1,73 @@
import { nodes } from 'prosemirror-schema-basic'
import { Node, NodeSpec } from 'prosemirror-model'
import { orderedList, bulletList, listItem } from 'prosemirror-schema-list'
import { listItem as _listItem } from 'prosemirror-schema-list'
const _orderedList: NodeSpec = {
...orderedList,
const orderedList: NodeSpec = {
attrs: {
order: {
default: 1,
},
listStyleType: {
default: '',
},
},
content: 'list_item+',
group: 'block',
parseDOM: [
{
tag: 'ol',
getAttrs: dom => {
const order = ((dom as HTMLElement).hasAttribute('start') ? (dom as HTMLElement).getAttribute('start') : 1) || 1
const attr = { order: +order }
const { listStyleType } = (dom as HTMLElement).style
if (listStyleType) attr['listStyleType'] = listStyleType
return attr
}
}
],
toDOM: (node: Node) => {
const { order, listStyleType } = node.attrs
let style = ''
if (listStyleType) style += `list-style-type: ${listStyleType};`
const attr = { style }
if (order !== 1) attr['start'] = order
return ['ol', attr, 0]
},
}
const _bulletList: NodeSpec = {
...bulletList,
const bulletList: NodeSpec = {
attrs: {
listStyleType: {
default: '',
},
},
content: 'list_item+',
group: 'block',
parseDOM: [
{
tag: 'ul',
getAttrs: dom => {
const { listStyleType } = (dom as HTMLElement).style
return listStyleType ? { listStyleType } : {}
}
}
],
toDOM: (node: Node) => {
const { listStyleType } = node.attrs
let style = ''
if (listStyleType) style += `list-style-type: ${listStyleType};`
return ['ul', { style }, 0]
},
}
const _listItem: NodeSpec = {
...listItem,
const listItem: NodeSpec = {
..._listItem,
content: 'paragraph block*',
group: 'block',
}
@ -63,8 +115,8 @@ const { hard_break, ...otherNodes } = nodes
export default {
...otherNodes,
'ordered_list': _orderedList,
'bullet_list': _bulletList,
'list_item': _listItem,
'ordered_list': orderedList,
'bullet_list': bulletList,
'list_item': listItem,
paragraph,
}

View File

@ -200,22 +200,57 @@
</Tooltip>
</RadioGroup>
<CheckboxButtonGroup class="row">
<div class="row">
<ButtonGroup style="flex: 15;">
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="项目符号">
<CheckboxButton
<Button
:type="richTextAttrs.bulletList ? 'primary' : 'default'"
style="flex: 1;"
:checked="richTextAttrs.bulletList"
@click="emitRichTextCommand('bulletList')"
><IconList /></CheckboxButton>
><IconList /></Button>
</Tooltip>
<Popover trigger="click" v-model:visible="bulletListPanelVisible">
<template #content>
<div class="list-wrap">
<ul class="list"
v-for="item in bulletListStyleTypeOption"
:key="item"
:style="{ listStyleType: item }"
@click="emitRichTextCommand('bulletList', item)"
>
<li class="list-item" v-for="key in 3" :key="key"><span></span></li>
</ul>
</div>
</template>
<Button class="popover-btn"><IconDown /></Button>
</Popover>
</ButtonGroup>
<div style="flex: 1;"></div>
<ButtonGroup style="flex: 15;">
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="编号">
<CheckboxButton
<Button
:type="richTextAttrs.orderedList ? 'primary' : 'default'"
style="flex: 1;"
:checked="richTextAttrs.orderedList"
@click="emitRichTextCommand('orderedList')"
><IconOrderedList /></CheckboxButton>
><IconOrderedList /></Button>
</Tooltip>
</CheckboxButtonGroup>
<Popover trigger="click" v-model:visible="orderedListPanelVisible">
<template #content>
<div class="list-wrap">
<ul class="list"
v-for="item in orderedListStyleTypeOption"
:key="item"
:style="{ listStyleType: item }"
@click="emitRichTextCommand('orderedList', item)"
>
<li class="list-item" v-for="key in 3" :key="key"><span></span></li>
</ul>
</div>
</template>
<Button class="popover-btn"><IconDown /></Button>
</Popover>
</ButtonGroup>
</div>
<ButtonGroup class="row">
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="减小缩进">
@ -383,6 +418,12 @@ const updateElement = (props: Partial<PPTTextElement>) => {
addHistorySnapshot()
}
const bulletListPanelVisible = ref(false)
const orderedListPanelVisible = ref(false)
const bulletListStyleTypeOption = ref(['disc', 'circle', 'square'])
const orderedListStyleTypeOption = ref(['decimal', 'lower-roman', 'upper-roman', 'lower-alpha', 'upper-alpha', 'lower-greek'])
const fill = ref<string>('#000')
const lineHeight = ref<number>()
const wordSpace = ref<number>()
@ -515,4 +556,55 @@ const updateLink = (link?: string) => {
text-align: right;
}
}
.list-wrap {
width: 176px;
color: #666;
padding: 8px;
margin: -12px;
display: flex;
flex-wrap: wrap;
align-content: flex-start;
}
.list {
background-color: $lightGray;
padding: 4px 4px 4px 20px;
cursor: pointer;
&:not(:nth-child(3n)) {
margin-right: 8px;
}
&:nth-child(4),
&:nth-child(5),
&:nth-child(6) {
margin-top: 8px;
}
&:hover {
color: $themeColor;
span {
background-color: $themeColor;
}
}
}
.list-item {
width: 24px;
height: 12px;
position: relative;
top: -5px;
span {
width: 100%;
height: 2px;
display: inline-block;
position: absolute;
top: 10px;
background-color: #666;
}
}
.popover-btn {
padding: 0 3px;
}
</style>

View File

@ -190,12 +190,14 @@ const execCommand = ({ target, action }: RichTextCommand) => {
indentCommand(editorView, +item.value)
}
else if (item.command === 'bulletList') {
const listStyleType = item.value || ''
const { bullet_list: bulletList, list_item: listItem } = editorView.state.schema.nodes
toggleList(bulletList, listItem)(editorView.state, editorView.dispatch)
toggleList(bulletList, listItem, listStyleType)(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)(editorView.state, editorView.dispatch)
toggleList(orderedList, listItem, listStyleType)(editorView.state, editorView.dispatch)
}
else if (item.command === 'clear') {
autoSelectAll(editorView)