mirror of https://github.com/halo-dev/halo
feat: add tab shortcut function to the default editor table (#5784)
#### What type of PR is this? /kind feature /area editor #### What this PR does / why we need it: 为默认编辑器表格增加 `Tab` 与 `Shift-Tab` 切换至上一个单元格或下一个单元格。具体功能如下: 1. 使用 Tab 快捷键从左向右切换至下一个单元格,当光标在最后一个单元格时,使用 Tab 键新建一行并跳转至新一行的第一个单元格。 2. 使用 Shift + Tab 快捷键从右向左来切换至上一个单元格。 #### How to test it? 测试在默认编辑器中 `Tab` 快捷键切换单元格是否生效。 测试在合并单元格等各种表格操作下,切换单元格是否生效。 #### Which issue(s) this PR fixes: Fixes #5771 #### Does this PR introduce a user-facing change? ```release-note 为默认编辑器表格增加 Tab 快捷键切换单元格的功能 ```pull/5705/head^2
parent
c0de807b9e
commit
cb6836aa8c
|
@ -16,6 +16,7 @@ import {
|
|||
type NodeView,
|
||||
type EditorState,
|
||||
type DOMOutputSpec,
|
||||
TextSelection,
|
||||
} from "@/tiptap/pm";
|
||||
import TableCell from "./table-cell";
|
||||
import TableRow from "./table-row";
|
||||
|
@ -38,6 +39,8 @@ import { i18n } from "@/locales";
|
|||
import type { ExtensionOptions, NodeBubbleMenu } from "@/types";
|
||||
import { BlockActionSeparator, ToolboxItem } from "@/components";
|
||||
import {
|
||||
findNextCell,
|
||||
findPreviousCell,
|
||||
hasTableBefore,
|
||||
isCellSelection,
|
||||
isTableSelected,
|
||||
|
@ -522,6 +525,62 @@ const Table = TiptapTable.extend<ExtensionOptions & TableOptions>({
|
|||
editor.commands.setNodeSelection(cellNodePos.pos);
|
||||
return true;
|
||||
},
|
||||
Tab: ({ editor }) => {
|
||||
const { state } = editor;
|
||||
if (!isActive(editor.state, Table.name)) {
|
||||
return false;
|
||||
}
|
||||
let nextView = editor.view;
|
||||
let nextTr = editor.state.tr;
|
||||
|
||||
let nextCell = findNextCell(state);
|
||||
if (!nextCell) {
|
||||
// If it is the last cell, create a new line and jump to the first cell of the new line.
|
||||
editor
|
||||
.chain()
|
||||
.addRowAfter()
|
||||
.command(({ tr, view, state }) => {
|
||||
nextView = view;
|
||||
nextTr = tr;
|
||||
nextCell = findNextCell(state);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
if (nextCell) {
|
||||
nextTr.setSelection(
|
||||
new TextSelection(
|
||||
nextTr.doc.resolve(nextCell.start),
|
||||
nextTr.doc.resolve(
|
||||
nextCell.start + (nextCell.node?.nodeSize || 0) - 4
|
||||
)
|
||||
)
|
||||
);
|
||||
nextTr.scrollIntoView();
|
||||
nextView.dispatch(nextTr);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
"Shift-Tab": ({ editor }) => {
|
||||
const { tr } = editor.state;
|
||||
if (!isActive(editor.state, Table.name)) {
|
||||
return false;
|
||||
}
|
||||
const previousCell = findPreviousCell(editor.state);
|
||||
if (previousCell) {
|
||||
tr.setSelection(
|
||||
new TextSelection(
|
||||
tr.doc.resolve(previousCell.start),
|
||||
tr.doc.resolve(
|
||||
previousCell.start + (previousCell.node?.nodeSize || 0) - 4
|
||||
)
|
||||
)
|
||||
);
|
||||
tr.scrollIntoView();
|
||||
editor.view.dispatch(tr);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { findParentNode } from "@/tiptap/vue-3";
|
||||
import { Node, CellSelection, TableMap } from "@/tiptap/pm";
|
||||
import type { EditorState, Selection, Transaction } from "@/tiptap/pm";
|
||||
import { Node, CellSelection, TableMap, selectedRect } from "@/tiptap/pm";
|
||||
import type { EditorState, Rect, Selection, Transaction } from "@/tiptap/pm";
|
||||
|
||||
export const selectTable = (tr: Transaction) => {
|
||||
const table = findTable(tr.selection);
|
||||
|
@ -250,3 +250,130 @@ export const hasTableBefore = (editorState: EditorState) => {
|
|||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const findNextCell = (state: EditorState) => {
|
||||
return findAdjacentCell(1)(state);
|
||||
};
|
||||
|
||||
export const findPreviousCell = (state: EditorState) => {
|
||||
return findAdjacentCell(-1)(state);
|
||||
};
|
||||
|
||||
export const findAdjacentCell = (dir: number) => (state: EditorState) => {
|
||||
const selectionPosRect = selectedRect(state);
|
||||
if (selectionPosRect.table) {
|
||||
const map = selectionPosRect.map;
|
||||
// currentPos is the position of the current cell in the table map, which is between two cells.
|
||||
const selectedCells = map.cellsInRect(selectionPosRect);
|
||||
// Get the currently selected cell boundary
|
||||
const rect = nextCell(map)(selectedCells[selectedCells.length - 1], dir);
|
||||
if (rect) {
|
||||
const { top, left } = rect;
|
||||
// Get the pos of the current cell according to the boundary
|
||||
const nextPos = map.map[top * map.width + left];
|
||||
return {
|
||||
start: nextPos + selectionPosRect.tableStart + 2,
|
||||
node: selectionPosRect.table.nodeAt(nextPos),
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
export const nextCell = (map: TableMap) => (pos: number, dir: number) => {
|
||||
function findNextCellPos({ top, left, right, bottom }: Rect) {
|
||||
const nextCellRect = {
|
||||
top,
|
||||
left,
|
||||
right,
|
||||
bottom,
|
||||
};
|
||||
if (right + 1 > map.width) {
|
||||
if (bottom === map.height) {
|
||||
return undefined;
|
||||
}
|
||||
nextCellRect.top++;
|
||||
nextCellRect.left = 0;
|
||||
nextCellRect.right = 1;
|
||||
nextCellRect.bottom++;
|
||||
} else {
|
||||
nextCellRect.left++;
|
||||
nextCellRect.right++;
|
||||
}
|
||||
const temporaryPos =
|
||||
map.map[nextCellRect.top * map.width + nextCellRect.left];
|
||||
const temporaryRect = map.findCell(temporaryPos);
|
||||
if (
|
||||
temporaryRect.top != nextCellRect.top ||
|
||||
temporaryRect.left < nextCellRect.left
|
||||
) {
|
||||
return findNextCellPos({
|
||||
...nextCellRect,
|
||||
right: temporaryRect.right,
|
||||
});
|
||||
}
|
||||
return temporaryPos;
|
||||
}
|
||||
|
||||
function findPreviousCellPos({ top, left, right, bottom }: Rect) {
|
||||
const nextCellRect = {
|
||||
top,
|
||||
left,
|
||||
right,
|
||||
bottom,
|
||||
};
|
||||
if (left - 1 < 0) {
|
||||
if (top === 0) {
|
||||
return undefined;
|
||||
}
|
||||
nextCellRect.top--;
|
||||
nextCellRect.left = map.width - 1;
|
||||
nextCellRect.right = map.width;
|
||||
nextCellRect.bottom--;
|
||||
} else {
|
||||
nextCellRect.left--;
|
||||
nextCellRect.right--;
|
||||
}
|
||||
const temporaryPos =
|
||||
map.map[nextCellRect.top * map.width + nextCellRect.left];
|
||||
const temporaryRect = map.findCell(temporaryPos);
|
||||
if (temporaryRect.top != nextCellRect.top) {
|
||||
return findPreviousCellPos(nextCellRect);
|
||||
}
|
||||
return temporaryPos;
|
||||
}
|
||||
|
||||
function nextCellRectByPos(innerPos: number, innerDir: number) {
|
||||
// Get the current cell boundary
|
||||
const { top, left, right, bottom } = map.findCell(innerPos);
|
||||
if (innerDir == 0) {
|
||||
return {
|
||||
top,
|
||||
left,
|
||||
right,
|
||||
bottom,
|
||||
};
|
||||
}
|
||||
|
||||
const nextCellRect = {
|
||||
top,
|
||||
left,
|
||||
right,
|
||||
bottom,
|
||||
};
|
||||
let nextPos;
|
||||
if (innerDir > 0) {
|
||||
nextPos = findNextCellPos(nextCellRect);
|
||||
innerDir--;
|
||||
} else {
|
||||
nextPos = findPreviousCellPos(nextCellRect);
|
||||
innerDir++;
|
||||
}
|
||||
if (!nextPos) {
|
||||
return undefined;
|
||||
}
|
||||
return nextCellRectByPos(nextPos, innerDir);
|
||||
}
|
||||
|
||||
return nextCellRectByPos(pos, dir);
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue