mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 21:27:20 +00:00
Closes: [BS-2216](https://linear.app/affine-design/issue/BS-2216/remove-global-types-in-command)
129 lines
3.3 KiB
TypeScript
129 lines
3.3 KiB
TypeScript
import {
|
|
calculateCollapsedSiblings,
|
|
getNearestHeadingBefore,
|
|
matchFlavours,
|
|
} from '@blocksuite/affine-shared/utils';
|
|
import { type Command, TextSelection } from '@blocksuite/block-std';
|
|
|
|
import { indentBlock } from './indent-block';
|
|
|
|
export const indentBlocks: Command<{
|
|
blockIds?: string[];
|
|
stopCapture?: boolean;
|
|
}> = (ctx, next) => {
|
|
let { blockIds } = ctx;
|
|
const { std, stopCapture = true } = ctx;
|
|
const { store, selection, range, host } = std;
|
|
const { schema } = store;
|
|
|
|
if (!blockIds || !blockIds.length) {
|
|
const nativeRange = range.value;
|
|
if (nativeRange) {
|
|
const topBlocks = range.getSelectedBlockComponentsByRange(nativeRange, {
|
|
match: el => el.model.role === 'content',
|
|
mode: 'highest',
|
|
});
|
|
if (topBlocks.length > 0) {
|
|
blockIds = topBlocks.map(block => block.blockId);
|
|
}
|
|
} else {
|
|
blockIds = std.selection.getGroup('note').map(sel => sel.blockId);
|
|
}
|
|
}
|
|
|
|
if (!blockIds || !blockIds.length || store.readonly) return;
|
|
|
|
// Find the first model that can be indented
|
|
let firstIndentIndex = -1;
|
|
for (let i = 0; i < blockIds.length; i++) {
|
|
const previousSibling = store.getPrev(blockIds[i]);
|
|
const model = store.getBlock(blockIds[i])?.model;
|
|
if (
|
|
model &&
|
|
previousSibling &&
|
|
schema.isValid(model.flavour, previousSibling.flavour)
|
|
) {
|
|
firstIndentIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// No model can be indented
|
|
if (firstIndentIndex === -1) return;
|
|
|
|
if (stopCapture) store.captureSync();
|
|
|
|
const collapsedIds: string[] = [];
|
|
blockIds.slice(firstIndentIndex).forEach(id => {
|
|
const model = store.getBlock(id)?.model;
|
|
if (!model) return;
|
|
if (
|
|
matchFlavours(model, ['affine:paragraph']) &&
|
|
model.type.startsWith('h') &&
|
|
model.collapsed
|
|
) {
|
|
const collapsedSiblings = calculateCollapsedSiblings(model);
|
|
collapsedIds.push(...collapsedSiblings.map(sibling => sibling.id));
|
|
}
|
|
});
|
|
// Models waiting to be indented
|
|
const indentIds = blockIds
|
|
.slice(firstIndentIndex)
|
|
.filter(id => !collapsedIds.includes(id));
|
|
const firstModel = store.getBlock(indentIds[0])?.model;
|
|
if (!firstModel) return;
|
|
|
|
{
|
|
// > # 123
|
|
// > # 456
|
|
// > # 789
|
|
//
|
|
// we need to update 123 collapsed state to false when indent 456 and 789
|
|
|
|
const nearestHeading = getNearestHeadingBefore(firstModel);
|
|
if (
|
|
nearestHeading &&
|
|
matchFlavours(nearestHeading, ['affine:paragraph']) &&
|
|
nearestHeading.collapsed
|
|
) {
|
|
store.updateBlock(nearestHeading, {
|
|
collapsed: false,
|
|
});
|
|
}
|
|
}
|
|
|
|
indentIds.forEach(id => {
|
|
std.command.exec(indentBlock, { blockId: id, stopCapture: false });
|
|
});
|
|
|
|
{
|
|
// 123
|
|
// > # 456
|
|
// 789
|
|
// 012
|
|
//
|
|
// we need to update 456 collapsed state to false when indent 789 and 012
|
|
const nearestHeading = getNearestHeadingBefore(firstModel);
|
|
if (
|
|
nearestHeading &&
|
|
matchFlavours(nearestHeading, ['affine:paragraph']) &&
|
|
nearestHeading.collapsed
|
|
) {
|
|
store.updateBlock(nearestHeading, {
|
|
collapsed: false,
|
|
});
|
|
}
|
|
}
|
|
|
|
const textSelection = selection.find(TextSelection);
|
|
if (textSelection) {
|
|
host.updateComplete
|
|
.then(() => {
|
|
range.syncTextSelectionToRange(textSelection);
|
|
})
|
|
.catch(console.error);
|
|
}
|
|
|
|
return next();
|
|
};
|