feat: add the first line indent to the Tab shortcut key (#6388)

#### What type of PR is this?

/kind feature
/area editor

#### What this PR does / why we need it:

支持在默认编辑器中使用 Tab 键实现首行缩进的功能。

当光标处于文本首行时,按下 Tab 键会触发首行缩进。
在首行缩进的情况下或者选中一段文本,再次按下 Tab 键会触发整段缩进。

#### How to test it?

测试文本块及区域标题块首行按下 Tab 键是否可以正常触发首行缩进

#### Which issue(s) this PR fixes:

Fixes #6316 

#### Does this PR introduce a user-facing change?
```release-note
默认编辑器增加 Tab 快捷键首行缩进功能
```
pull/6408/head
Takagi 2024-07-29 12:47:52 +08:00 committed by GitHub
parent 687de1c550
commit 59b3f460fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 48 additions and 23 deletions

View File

@ -24,6 +24,7 @@ type IndentOptions = {
maxIndentLevel: number; maxIndentLevel: number;
defaultIndentLevel: number; defaultIndentLevel: number;
HTMLAttributes: Record<string, any>; HTMLAttributes: Record<string, any>;
firstLineIndent: boolean;
}; };
const Indent = Extension.create<IndentOptions, never>({ const Indent = Extension.create<IndentOptions, never>({
name: "indent", name: "indent",
@ -36,6 +37,7 @@ const Indent = Extension.create<IndentOptions, never>({
maxIndentLevel: 24 * 10, maxIndentLevel: 24 * 10,
defaultIndentLevel: 0, defaultIndentLevel: 0,
HTMLAttributes: {}, HTMLAttributes: {},
firstLineIndent: true,
}; };
}, },
@ -56,6 +58,13 @@ const Indent = Extension.create<IndentOptions, never>({
parseInt(element.style.marginLeft, 10) || parseInt(element.style.marginLeft, 10) ||
this.options.defaultIndentLevel, this.options.defaultIndentLevel,
}, },
lineIndent: {
default: false,
renderHTML: (attributes) => ({
style: attributes.lineIndent ? "text-indent: 2em" : "",
}),
parseHTML: (element) => element.style.textIndent === "2em",
},
}, },
}, },
]; ];
@ -76,9 +85,8 @@ const Indent = Extension.create<IndentOptions, never>({
); );
if (tr.docChanged && dispatch) { if (tr.docChanged && dispatch) {
dispatch(tr); dispatch(tr);
return true;
} }
return false; return true;
}, },
outdent: outdent:
() => () =>
@ -93,9 +101,8 @@ const Indent = Extension.create<IndentOptions, never>({
); );
if (tr.docChanged && dispatch) { if (tr.docChanged && dispatch) {
dispatch(tr); dispatch(tr);
return true;
} }
return false; return true;
}, },
}; };
}, },
@ -133,22 +140,48 @@ export const clamp = (val: number, min: number, max: number): number => {
function setNodeIndentMarkup( function setNodeIndentMarkup(
tr: Transaction, tr: Transaction,
pos: number, pos: number,
delta: number, dir: number,
min: number, options: IndentOptions
max: number
): Transaction { ): Transaction {
if (!tr.doc) return tr; if (!tr.doc) {
return tr;
}
const node = tr.doc.nodeAt(pos); const node = tr.doc.nodeAt(pos);
if (!node) return tr; if (!node) {
return tr;
}
if (options.firstLineIndent && isLineIndent(tr)) {
if (node.attrs.lineIndent !== dir > 0) {
const nodeAttrs = { ...node.attrs, lineIndent: dir > 0 };
return tr.setNodeMarkup(pos, node.type, nodeAttrs, node.marks);
}
}
const delta = options.indentRange * dir;
const min = options.minIndentLevel;
const max = options.maxIndentLevel;
const indent = clamp((node.attrs.indent || 0) + delta, min, max); const indent = clamp((node.attrs.indent || 0) + delta, min, max);
if (indent === node.attrs.indent) return tr; if (indent === node.attrs.indent) {
const nodeAttrs = { return tr;
...node.attrs, }
indent, const nodeAttrs = { ...node.attrs, indent };
};
return tr.setNodeMarkup(pos, node.type, nodeAttrs, node.marks); return tr.setNodeMarkup(pos, node.type, nodeAttrs, node.marks);
} }
const isLineIndent = (tr: Transaction) => {
const { selection } = tr;
const { $from, from, to } = selection;
if (from == 0) {
return true;
}
if (from != to) {
return false;
}
return $from.textOffset == 0;
};
type IndentType = "indent" | "outdent"; type IndentType = "indent" | "outdent";
const updateIndentLevel = ( const updateIndentLevel = (
tr: Transaction, tr: Transaction,
@ -167,13 +200,7 @@ const updateIndentLevel = (
if (isTextIndent(tr, pos) && type === "indent") { if (isTextIndent(tr, pos) && type === "indent") {
tr.insertText("\t", from, to); tr.insertText("\t", from, to);
} else { } else {
tr = setNodeIndentMarkup( tr = setNodeIndentMarkup(tr, pos, type === "indent" ? 1 : -1, options);
tr,
pos,
options.indentRange * (type === "indent" ? 1 : -1),
options.minIndentLevel,
options.maxIndentLevel
);
} }
return false; return false;
} }
@ -210,11 +237,9 @@ const isFilterActive = (editor: CoreEditor) => {
export const getIndent: () => KeyboardShortcutCommand = export const getIndent: () => KeyboardShortcutCommand =
() => () =>
({ editor }) => { ({ editor }) => {
// @ts-ignore
if (isFilterActive(editor)) { if (isFilterActive(editor)) {
return false; return false;
} }
// @ts-ignore
if (isListActive(editor)) { if (isListActive(editor)) {
const name = editor.can().sinkListItem("listItem") const name = editor.can().sinkListItem("listItem")
? "listItem" ? "listItem"