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 @@ + + + + + + + + +
diff --git a/src/views/components/element/ProsemirrorEditor.vue b/src/views/components/element/ProsemirrorEditor.vue index dbc4a897..a45fb954 100644 --- a/src/views/components/element/ProsemirrorEditor.vue +++ b/src/views/components/element/ProsemirrorEditor.vue @@ -16,6 +16,7 @@ import { initProsemirrorEditor, createDocument } from '@/utils/prosemirror' import { findNodesWithSameMark, getTextAttrs, autoSelectAll, addMark, markActive, getFontsize } from '@/utils/prosemirror/utils' import emitter, { EmitterEvents, RichTextCommand } from '@/utils/emitter' import { alignmentCommand } from '@/utils/prosemirror/commands/setTextAlign' +import { indentCommand } from '@/utils/prosemirror/commands/setTextIndent' import { toggleList } from '@/utils/prosemirror/commands/toggleList' export default defineComponent({ @@ -192,6 +193,9 @@ export default defineComponent({ else if (item.command === 'align' && item.value) { alignmentCommand(editorView, item.value) } + else if (item.command === 'indent' && item.value) { + indentCommand(editorView, +item.value) + } else if (item.command === 'bulletList') { const { bullet_list: bulletList, list_item: listItem } = editorView.state.schema.nodes toggleList(bulletList, listItem)(editorView.state, editorView.dispatch)