添加列表样式及缩进菜单
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2024-present AI-Labs
|
||||
|
||||
import BulletList from '@tiptap/extension-bullet-list'
|
||||
|
||||
// https://www.npmjs.com/package/tiptap-extension-bullet-list
|
||||
export default BulletList.extend({
|
||||
content: 'listItem*',
|
||||
addAttributes() {
|
||||
return {
|
||||
...this.parent?.(),
|
||||
listType: {
|
||||
default: 'disc',
|
||||
parseHTML: (element) =>
|
||||
element.style.getPropertyValue('list-style-type') || 'disc',
|
||||
renderHTML: ({ listType }) => {
|
||||
return {
|
||||
style: `list-style-type: ${listType}`,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,147 @@
|
||||
// Copyright (c) 2024-present AI-Labs
|
||||
|
||||
import { Command, Extension } from '@tiptap/core';
|
||||
import { AllSelection, TextSelection, Transaction } from 'prosemirror-state';
|
||||
|
||||
export interface IndentOptions {
|
||||
types: string[];
|
||||
minLevel: number;
|
||||
maxLevel: number;
|
||||
}
|
||||
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands<ReturnType> {
|
||||
indent: {
|
||||
/**
|
||||
* 向右缩进
|
||||
* @returns
|
||||
*/
|
||||
indent: () => ReturnType;
|
||||
/**
|
||||
* 向左缩进
|
||||
* @returns
|
||||
*/
|
||||
outdent: () => ReturnType;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 缩进样式前缀
|
||||
*/
|
||||
const classAttrPrefix = 'indent-';
|
||||
|
||||
export default Extension.create<IndentOptions>({
|
||||
name: 'indent',
|
||||
|
||||
priority: 99,
|
||||
|
||||
addOptions() {
|
||||
return {
|
||||
types: ['heading', 'listItem', 'taskItem', 'paragraph'],
|
||||
minLevel: 0,
|
||||
maxLevel: 8,
|
||||
}
|
||||
},
|
||||
|
||||
addGlobalAttributes() {
|
||||
return [
|
||||
{
|
||||
types: this.options.types,
|
||||
attributes: {
|
||||
indent: {
|
||||
default: 0,
|
||||
parseHTML: element => {
|
||||
let level = 0
|
||||
for (const className of element.classList) {
|
||||
if (className.startsWith(classAttrPrefix)) {
|
||||
level = +(className.split("-")[1])
|
||||
break
|
||||
}
|
||||
}
|
||||
return level && level > this.options.minLevel ? level : null;
|
||||
},
|
||||
|
||||
renderHTML: attributes => {
|
||||
if (!attributes.indent) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return {
|
||||
class: `${classAttrPrefix}${attributes.indent}`,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
const setNodeIndentMarkup = (tr: Transaction, pos: number, delta: number): Transaction => {
|
||||
const node = tr?.doc?.nodeAt(pos);
|
||||
|
||||
if (node) {
|
||||
const nextLevel = (node.attrs.indent || 0) + delta;
|
||||
const { minLevel, maxLevel } = this.options;
|
||||
const indent = nextLevel < minLevel ? minLevel : nextLevel > maxLevel ? maxLevel : nextLevel;
|
||||
|
||||
if (indent !== node.attrs.indent) {
|
||||
const { indent: oldIndent, ...currentAttrs } = node.attrs;
|
||||
const nodeAttrs = indent > minLevel ? { ...currentAttrs, indent } : currentAttrs;
|
||||
return tr.setNodeMarkup(pos, node.type, nodeAttrs, node.marks);
|
||||
}
|
||||
}
|
||||
return tr;
|
||||
};
|
||||
|
||||
const updateIndentLevel = (tr: Transaction, delta: number): Transaction => {
|
||||
const { doc, selection } = tr;
|
||||
|
||||
if (doc && selection && (selection instanceof TextSelection || selection instanceof AllSelection)) {
|
||||
const { from, to } = selection;
|
||||
doc.nodesBetween(from, to, (node, pos) => {
|
||||
if (this.options.types.includes(node.type.name)) {
|
||||
tr = setNodeIndentMarkup(tr, pos, delta);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
return tr;
|
||||
};
|
||||
|
||||
const applyIndent: (direction: number) => () => Command =
|
||||
direction =>
|
||||
() =>
|
||||
({ tr, state, dispatch }) => {
|
||||
const { selection } = state;
|
||||
tr = tr.setSelection(selection);
|
||||
tr = updateIndentLevel(tr, direction);
|
||||
|
||||
if (tr.docChanged) {
|
||||
dispatch?.(tr);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
return {
|
||||
indent: applyIndent(1),
|
||||
outdent: applyIndent(-1),
|
||||
};
|
||||
},
|
||||
|
||||
addKeyboardShortcuts() {
|
||||
return {
|
||||
Tab: () => {
|
||||
return this.editor.commands.indent();
|
||||
},
|
||||
'Shift-Tab': () => {
|
||||
return this.editor.commands.outdent();
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2024-present AI-Labs
|
||||
|
||||
import { Extension } from '@tiptap/core'
|
||||
|
||||
export interface LineHeightOptions {
|
||||
types: string[];
|
||||
defaultLineHeight: number;
|
||||
}
|
||||
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands<ReturnType> {
|
||||
setLineHeight: {
|
||||
/**
|
||||
* 设置行高
|
||||
* @param lineHeight
|
||||
* @returns
|
||||
*/
|
||||
setLineHeight: (lineHeight: number) => ReturnType
|
||||
}
|
||||
unsetLineHeight: {
|
||||
/**
|
||||
* 取消设置行高
|
||||
* @returns
|
||||
*/
|
||||
unsetLineHeight: () => ReturnType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Extension.create<LineHeightOptions>({
|
||||
name: 'lineHeight',
|
||||
addOptions() {
|
||||
return {
|
||||
types: ["heading", "paragraph"],
|
||||
defaultLineHeight: 1.5,
|
||||
}
|
||||
},
|
||||
addGlobalAttributes() {
|
||||
return [
|
||||
{
|
||||
types: this.options.types,
|
||||
attributes: {
|
||||
lineHeight: {
|
||||
default: this.options.defaultLineHeight,
|
||||
parseHTML: (element) =>
|
||||
element.style.lineHeight || this.options.defaultLineHeight,
|
||||
renderHTML: (attributes) => {
|
||||
if (attributes.lineHeight === this.options.defaultLineHeight) {
|
||||
return {}
|
||||
}
|
||||
return { style: `line-height: ${attributes.lineHeight}` }
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
addCommands() {
|
||||
return {
|
||||
setLineHeight:
|
||||
(lineHeight) =>
|
||||
({ commands }) => {
|
||||
return this.options.types.every((type: string) =>
|
||||
commands.updateAttributes(type, { lineHeight: lineHeight }),
|
||||
)
|
||||
},
|
||||
unsetLineHeight:
|
||||
() =>
|
||||
({ commands }) => {
|
||||
return this.options.types.every((type: string) =>
|
||||
commands.resetAttributes(type, 'lineHeight'),
|
||||
)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2024-present AI-Labs
|
||||
|
||||
import OrderedList from '@tiptap/extension-ordered-list'
|
||||
|
||||
// https://www.npmjs.com/package/tiptap-extension-ordered-list
|
||||
export default OrderedList.extend({
|
||||
content: 'listItem*',
|
||||
addAttributes() {
|
||||
return {
|
||||
...this.parent?.(),
|
||||
listType: {
|
||||
default: 'decimal',
|
||||
parseHTML: (element) =>
|
||||
element.style.getPropertyValue('list-style-type') || 'decimal',
|
||||
renderHTML: ({ listType }) => {
|
||||
return {
|
||||
style: `list-style-type: ${listType}`,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user