diff --git a/src/assets/styles/prosemirror.scss b/src/assets/styles/prosemirror.scss
index c25bbc24..cc43fca5 100644
--- a/src/assets/styles/prosemirror.scss
+++ b/src/assets/styles/prosemirror.scss
@@ -54,6 +54,28 @@
font-style: italic;
border-left: 5px solid #ccc;
}
+
+ [data-indent='1'] {
+ padding-left: 48px;
+ }
+ [data-indent='2'] {
+ padding-left: 96px;
+ }
+ [data-indent='3'] {
+ padding-left: 144px;
+ }
+ [data-indent='4'] {
+ padding-left: 192px;
+ }
+ [data-indent='5'] {
+ padding-left: 240px;
+ }
+ [data-indent='6'] {
+ padding-left: 288px;
+ }
+ [data-indent='7'] {
+ padding-left: 336px;
+ }
}
.ProseMirror-selectednode {
diff --git a/src/hooks/useExport.ts b/src/hooks/useExport.ts
index c7abd8d4..d3b4c18b 100644
--- a/src/hooks/useExport.ts
+++ b/src/hooks/useExport.ts
@@ -74,6 +74,7 @@ export default () => {
const formatHTML = (html: string) => {
const ast = toAST(html)
let bulletFlag = false
+ let indent = 0
const slices: pptxgen.TextProps[] = []
const parse = (obj: AST[], baseStyleObj = {}) => {
@@ -124,6 +125,12 @@ export default () => {
if (item.tagName === 'li') {
bulletFlag = true
}
+ if (item.tagName === 'p') {
+ if ('attributes' in item) {
+ const dataIndentAttr = item.attributes.find(attr => attr.key === 'data-indent')
+ if (dataIndentAttr && dataIndentAttr.value) indent = +dataIndentAttr.value
+ }
+ }
}
if ('tagName' in item && item.tagName === 'br') {
@@ -184,6 +191,10 @@ export default () => {
options.paraSpaceBefore = 0.1
bulletFlag = false
}
+ if (indent) {
+ options.indentLevel = indent
+ indent = 0
+ }
slices.push({ text, options })
}
diff --git a/src/plugins/icon.ts b/src/plugins/icon.ts
index b1656254..ad1b9d41 100644
--- a/src/plugins/icon.ts
+++ b/src/plugins/icon.ts
@@ -98,6 +98,8 @@ import {
Magic,
HighLight,
Share,
+ IndentLeft,
+ IndentRight,
} from '@icon-park/vue-next'
const icons = {
@@ -197,6 +199,8 @@ const icons = {
Magic,
HighLight,
Share,
+ IndentLeft,
+ IndentRight,
}
export default {
diff --git a/src/utils/prosemirror/commands/setTextIndent.ts b/src/utils/prosemirror/commands/setTextIndent.ts
new file mode 100644
index 00000000..5d5db289
--- /dev/null
+++ b/src/utils/prosemirror/commands/setTextIndent.ts
@@ -0,0 +1,66 @@
+import { Schema } from 'prosemirror-model'
+import { TextSelection, AllSelection, Transaction } from 'prosemirror-state'
+import { EditorView } from 'prosemirror-view'
+import { isList } from './toggleList'
+
+function setNodeIndentMarkup(tr: Transaction, pos: number, delta: number): Transaction {
+ if (!tr.doc) return tr
+
+ const node = tr.doc.nodeAt(pos)
+ if (!node) return tr
+
+ const minIndent = 0
+ const maxIndent = 7
+
+ let indent = (node.attrs.indent || 0) + delta
+ if (indent < minIndent) indent = minIndent
+ if (indent > maxIndent) indent = maxIndent
+
+ if (indent === node.attrs.indent) return tr
+
+ const nodeAttrs = {
+ ...node.attrs,
+ indent,
+ }
+
+ return tr.setNodeMarkup(pos, node.type, nodeAttrs, node.marks)
+}
+
+const setTextIndent = (tr: Transaction, schema: Schema, delta: number): Transaction => {
+ const { selection, doc } = tr
+ if (!selection || !doc) return tr
+
+ if (!(selection instanceof TextSelection || selection instanceof AllSelection)) return tr
+
+ const { from, to } = selection
+
+ doc.nodesBetween(from, to, (node, pos) => {
+ const nodeType = node.type
+
+ if (nodeType.name === 'paragraph' || nodeType.name === 'blockquote') {
+ tr = setNodeIndentMarkup(tr, pos, delta)
+ return false
+ }
+ else if (isList(node, schema)) return false
+ return true
+ })
+
+ return tr
+}
+
+export const indentCommand = (view: EditorView, delta: number) => {
+ const { state } = view
+ const { schema, selection } = state
+
+ const tr = setTextIndent(
+ state.tr.setSelection(selection),
+ schema,
+ delta,
+ )
+ if (tr.docChanged) {
+ view.dispatch(tr)
+ return true
+ }
+
+ return false
+}
\ No newline at end of file
diff --git a/src/utils/prosemirror/commands/toggleList.ts b/src/utils/prosemirror/commands/toggleList.ts
index 0812dcfc..1f621619 100644
--- a/src/utils/prosemirror/commands/toggleList.ts
+++ b/src/utils/prosemirror/commands/toggleList.ts
@@ -3,7 +3,7 @@ import { Schema, Node, NodeType } from 'prosemirror-model'
import { Transaction, EditorState } from 'prosemirror-state'
import { findParentNode } from '../utils'
-const isList = (node: Node, schema: Schema) => {
+export const isList = (node: Node, schema: Schema) => {
return (
node.type === schema.nodes.bullet_list ||
node.type === schema.nodes.ordered_list
diff --git a/src/utils/prosemirror/schema/nodes.ts b/src/utils/prosemirror/schema/nodes.ts
index 3ed10a96..c4b1c447 100644
--- a/src/utils/prosemirror/schema/nodes.ts
+++ b/src/utils/prosemirror/schema/nodes.ts
@@ -25,6 +25,9 @@ const paragraph: NodeSpec = {
align: {
default: '',
},
+ indent: {
+ default: 0,
+ },
},
content: 'inline*',
group: 'block',
@@ -33,19 +36,25 @@ const paragraph: NodeSpec = {
tag: 'p',
getAttrs: dom => {
const { textAlign } = (dom as HTMLElement).style
+
let align = (dom as HTMLElement).getAttribute('align') || textAlign || ''
align = /(left|right|center|justify)/.test(align) ? align : ''
+
+ const indent = +((dom as HTMLElement).getAttribute('data-indent') || 0)
- return { align }
+ return { align, indent }
}
}
],
toDOM: (node: Node) => {
- const { align } = node.attrs
+ const { align, indent } = node.attrs
let style = ''
if (align && align !== 'left') style += `text-align: ${align};`
- return ['p', { style }, 0]
+ const attr = { style }
+ if (indent) attr['data-indent'] = indent
+
+ return ['p', attr, 0]
},
}
diff --git a/src/views/Editor/Toolbar/ElementStylePanel/TextStylePanel.vue b/src/views/Editor/Toolbar/ElementStylePanel/TextStylePanel.vue
index 5a0359a6..5b06ba65 100644
--- a/src/views/Editor/Toolbar/ElementStylePanel/TextStylePanel.vue
+++ b/src/views/Editor/Toolbar/ElementStylePanel/TextStylePanel.vue
@@ -209,6 +209,15 @@
+