feat: 支持段落首行缩进

This commit is contained in:
pipipi-pikachu 2023-07-30 14:27:58 +08:00
parent 399abc0859
commit 8ff70da477
8 changed files with 106 additions and 54 deletions

View File

@ -17,7 +17,6 @@
}
p {
text-indent: var(--textIndent);
margin-top: var(--paragraphSpace);
}
p:first-child {
@ -61,25 +60,28 @@
}
[data-indent='1'] {
padding-left: 48px;
padding-left: 24px;
}
[data-indent='2'] {
padding-left: 96px;
padding-left: 48px;
}
[data-indent='3'] {
padding-left: 144px;
padding-left: 72px;
}
[data-indent='4'] {
padding-left: 192px;
padding-left: 96px;
}
[data-indent='5'] {
padding-left: 240px;
padding-left: 120px;
}
[data-indent='6'] {
padding-left: 288px;
padding-left: 144px;
}
[data-indent='7'] {
padding-left: 336px;
padding-left: 168px;
}
[data-indent='8'] {
padding-left: 192px;
}
}

View File

@ -140,8 +140,6 @@ interface PPTBaseElement {
*
* shadow?: 阴影
*
* textIndent?: 段落首行缩进
*
* paragraphSpace?: 段间距 5px
*
* vertical?: 竖向文本
@ -157,7 +155,6 @@ export interface PPTTextElement extends PPTBaseElement {
wordSpace?: number
opacity?: number
shadow?: PPTElementShadow
textIndent?: number
paragraphSpace?: number
vertical?: boolean
}

View File

@ -3,30 +3,32 @@ import { type Transaction, TextSelection, AllSelection } from 'prosemirror-state
import type { EditorView } from 'prosemirror-view'
import { isList } from './toggleList'
function setNodeIndentMarkup(tr: Transaction, pos: number, delta: number): Transaction {
type IndentKey = 'indent' | 'textIndent'
function setNodeIndentMarkup(tr: Transaction, pos: number, delta: number, indentKey: IndentKey): Transaction {
if (!tr.doc) return tr
const node = tr.doc.nodeAt(pos)
if (!node) return tr
const minIndent = 0
const maxIndent = 7
const maxIndent = 8
let indent = (node.attrs.indent || 0) + delta
let indent = (node.attrs[indentKey] || 0) + delta
if (indent < minIndent) indent = minIndent
if (indent > maxIndent) indent = maxIndent
if (indent === node.attrs.indent) return tr
if (indent === node.attrs[indentKey]) return tr
const nodeAttrs = {
...node.attrs,
indent,
[indentKey]: indent,
}
return tr.setNodeMarkup(pos, node.type, nodeAttrs, node.marks)
}
const setTextIndent = (tr: Transaction, schema: Schema, delta: number): Transaction => {
const setIndent = (tr: Transaction, schema: Schema, delta: number, indentKey: IndentKey): Transaction => {
const { selection, doc } = tr
if (!selection || !doc) return tr
@ -38,7 +40,7 @@ const setTextIndent = (tr: Transaction, schema: Schema, delta: number): Transact
const nodeType = node.type
if (nodeType.name === 'paragraph' || nodeType.name === 'blockquote') {
tr = setNodeIndentMarkup(tr, pos, delta)
tr = setNodeIndentMarkup(tr, pos, delta, indentKey)
return false
}
else if (isList(node, schema)) return false
@ -52,10 +54,29 @@ export const indentCommand = (view: EditorView, delta: number) => {
const { state } = view
const { schema, selection } = state
const tr = setTextIndent(
const tr = setIndent(
state.tr.setSelection(selection),
schema,
delta,
'indent',
)
if (tr.docChanged) {
view.dispatch(tr)
return true
}
return false
}
export const textIndentCommand = (view: EditorView, delta: number) => {
const { state } = view
const { schema, selection } = state
const tr = setIndent(
state.tr.setSelection(selection),
schema,
delta,
'textIndent',
)
if (tr.docChanged) {
view.dispatch(tr)

View File

@ -84,6 +84,9 @@ const paragraph: NodeSpec = {
indent: {
default: 0,
},
textIndent: {
default: 0,
},
},
content: 'inline*',
group: 'block',
@ -91,14 +94,20 @@ const paragraph: NodeSpec = {
{
tag: 'p',
getAttrs: dom => {
const { textAlign } = (dom as HTMLElement).style
const { textAlign, textIndent } = (dom as HTMLElement).style
let align = (dom as HTMLElement).getAttribute('align') || textAlign || ''
align = /(left|right|center|justify)/.test(align) ? align : ''
let textIndentLevel = 0
if (textIndent) {
textIndentLevel = Math.floor(parseInt(textIndent) / 24)
if (!textIndentLevel) textIndentLevel = 1
}
const indent = +((dom as HTMLElement).getAttribute('data-indent') || 0)
return { align, indent }
return { align, indent, textIndent: textIndentLevel }
}
},
{
@ -111,9 +120,10 @@ const paragraph: NodeSpec = {
},
],
toDOM: (node: Node) => {
const { align, indent } = node.attrs
const { align, indent, textIndent } = node.attrs
let style = ''
if (align && align !== 'left') style += `text-align: ${align};`
if (textIndent) style += `text-indent: ${textIndent * 24}px;`
const attr: Attr = { style }
if (indent) attr['data-indent'] = indent

View File

@ -256,14 +256,35 @@
</ButtonGroup>
</div>
<ButtonGroup class="row">
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="减小缩进">
<div class="row">
<ButtonGroup style="flex: 15;">
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="减小段落缩进">
<Button style="flex: 1;" @click="emitRichTextCommand('indent', '-1')"><IconIndentLeft /></Button>
</Tooltip>
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="增大缩进">
<Popover trigger="click" v-model:open="indentLeftPanelVisible">
<template #content>
<div class="popover-list">
<span class="popover-item" @click="emitRichTextCommand('textIndent', '-1')">减小首行缩进</span>
</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 style="flex: 1;" @click="emitRichTextCommand('indent', '+1')"><IconIndentRight /></Button>
</Tooltip>
<Popover trigger="click" v-model:open="indentRightPanelVisible">
<template #content>
<div class="popover-list">
<span class="popover-item" @click="emitRichTextCommand('textIndent', '+1')">增大首行缩进</span>
</div>
</template>
<Button class="popover-btn"><IconDown /></Button>
</Popover>
</ButtonGroup>
</div>
<Divider />
@ -288,13 +309,6 @@
<SelectOption v-for="item in wordSpaceOptions" :key="item" :value="item">{{item}}px</SelectOption>
</Select>
</div>
<div class="row">
<div style="flex: 2;">首行缩进</div>
<Select style="flex: 3;" :value="textIndent" @change="value => updateTextIndent(value as number)">
<template #suffixIcon><IconIndentRight /></template>
<SelectOption v-for="item in textIndentOptions" :key="item" :value="item">{{item}}px</SelectOption>
</Select>
</div>
<div class="row">
<div style="flex: 2;">文本框填充</div>
<Popover trigger="click">
@ -439,6 +453,8 @@ const updateElement = (props: Partial<PPTTextElement>) => {
const bulletListPanelVisible = ref(false)
const orderedListPanelVisible = ref(false)
const indentLeftPanelVisible = ref(false)
const indentRightPanelVisible = ref(false)
const bulletListStyleTypeOption = ref(['disc', 'circle', 'square'])
const orderedListStyleTypeOption = ref(['decimal', 'lower-roman', 'upper-roman', 'lower-alpha', 'upper-alpha', 'lower-greek'])
@ -446,7 +462,6 @@ const orderedListStyleTypeOption = ref(['decimal', 'lower-roman', 'upper-roman',
const fill = ref<string>('#000')
const lineHeight = ref<number>()
const wordSpace = ref<number>()
const textIndent = ref<number>()
const paragraphSpace = ref<number>()
watch(handleElement, () => {
@ -455,7 +470,6 @@ watch(handleElement, () => {
fill.value = handleElement.value.fill || '#fff'
lineHeight.value = handleElement.value.lineHeight || 1.5
wordSpace.value = handleElement.value.wordSpace || 0
textIndent.value = handleElement.value.textIndent || 0
paragraphSpace.value = handleElement.value.paragraphSpace === undefined ? 5 : handleElement.value.paragraphSpace
}, { deep: true, immediate: true })
@ -466,7 +480,6 @@ const fontSizeOptions = [
]
const lineHeightOptions = [0.9, 1.0, 1.15, 1.2, 1.4, 1.5, 1.8, 2.0, 2.5, 3.0]
const wordSpaceOptions = [0, 1, 2, 3, 4, 5, 6, 8, 10]
const textIndentOptions = [0, 48, 96, 144, 192, 240, 288, 336]
const paragraphSpaceOptions = [0, 5, 10, 15, 20, 25, 30, 40, 50, 80]
//
@ -484,11 +497,6 @@ const updateWordSpace = (value: number) => {
updateElement({ wordSpace: value })
}
//
const updateTextIndent = (value: number) => {
updateElement({ textIndent: value })
}
//
const updateFill = (value: string) => {
updateElement({ fill: value })
@ -626,6 +634,21 @@ const updateLink = (link?: string) => {
background-color: #666;
}
}
.popover-list {
display: flex;
flex-direction: column;
padding: 5px;
margin: -12px;
}
.popover-item {
padding: 9px 12px;
border-radius: 2px;
cursor: pointer;
&:hover {
background-color: #f5f5f5;
}
}
.popover-btn {
padding: 0 3px;
}

View File

@ -18,7 +18,7 @@ import { initProsemirrorEditor, createDocument } from '@/utils/prosemirror'
import { findNodesWithSameMark, getTextAttrs, autoSelectAll, addMark, markActive, getFontsize } from '@/utils/prosemirror/utils'
import emitter, { EmitterEvents, type RichTextAction, type RichTextCommand } from '@/utils/emitter'
import { alignmentCommand } from '@/utils/prosemirror/commands/setTextAlign'
import { indentCommand } from '@/utils/prosemirror/commands/setTextIndent'
import { indentCommand, textIndentCommand } from '@/utils/prosemirror/commands/setTextIndent'
import { toggleList } from '@/utils/prosemirror/commands/toggleList'
import type { TextFormatPainterKeys } from '@/types/edit'
@ -175,6 +175,9 @@ const execCommand = ({ target, action }: RichTextCommand) => {
else if (item.command === 'indent' && item.value) {
indentCommand(editorView, +item.value)
}
else if (item.command === 'textIndent' && item.value) {
textIndentCommand(editorView, +item.value)
}
else if (item.command === 'bulletList') {
const listStyleType = item.value || ''
const { bullet_list: bulletList, list_item: listItem } = editorView.state.schema.nodes

View File

@ -34,7 +34,9 @@
/>
<div
class="text ProseMirror-static"
:style="cssVar"
:style="{
'--paragraphSpace': `${elementInfo.paragraphSpace === undefined ? 5 : elementInfo.paragraphSpace}px`,
}"
v-html="elementInfo.content"
></div>
</div>
@ -43,7 +45,7 @@
</template>
<script lang="ts" setup>
import { computed, type StyleValue } from 'vue'
import { computed } from 'vue'
import type { PPTTextElement } from '@/types/slides'
import ElementOutline from '@/views/components/element/ElementOutline.vue'
@ -55,11 +57,6 @@ const props = defineProps<{
const shadow = computed(() => props.elementInfo.shadow)
const { shadowStyle } = useElementShadow(shadow)
const cssVar = computed(() => ({
'--textIndent': `${props.elementInfo.textIndent || 0}px`,
'--paragraphSpace': `${props.elementInfo.paragraphSpace === undefined ? 5 : props.elementInfo.paragraphSpace}px`,
} as StyleValue))
</script>
<style lang="scss" scoped>

View File

@ -45,7 +45,6 @@
:editable="!elementInfo.lock"
:value="elementInfo.content"
:style="{
'--textIndent': `${elementInfo.textIndent || 0}px`,
'--paragraphSpace': `${elementInfo.paragraphSpace === undefined ? 5 : elementInfo.paragraphSpace}px`,
}"
@update="value => updateContent(value)"