mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
feat: 文字项目符号支持多种样式
This commit is contained in:
parent
f4af2405dc
commit
10c6b3daa3
@ -48,8 +48,7 @@ body {
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
li {
|
||||
ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
}
|
||||
|
@ -200,22 +200,57 @@
|
||||
</Tooltip>
|
||||
</RadioGroup>
|
||||
|
||||
<CheckboxButtonGroup class="row">
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="项目符号">
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="richTextAttrs.bulletList"
|
||||
@click="emitRichTextCommand('bulletList')"
|
||||
><IconList /></CheckboxButton>
|
||||
</Tooltip>
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="编号">
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="richTextAttrs.orderedList"
|
||||
@click="emitRichTextCommand('orderedList')"
|
||||
><IconOrderedList /></CheckboxButton>
|
||||
</Tooltip>
|
||||
</CheckboxButtonGroup>
|
||||
<div class="row">
|
||||
<ButtonGroup style="flex: 15;">
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="项目符号">
|
||||
<Button
|
||||
:type="richTextAttrs.bulletList ? 'primary' : 'default'"
|
||||
style="flex: 1;"
|
||||
@click="emitRichTextCommand('bulletList')"
|
||||
><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="编号">
|
||||
<Button
|
||||
:type="richTextAttrs.orderedList ? 'primary' : 'default'"
|
||||
style="flex: 1;"
|
||||
@click="emitRichTextCommand('orderedList')"
|
||||
><IconOrderedList /></Button>
|
||||
</Tooltip>
|
||||
<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>
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user