Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(blocks): optimize the experience of switching selected blocks using ArrowUp and ArrowDown #6489

Merged
merged 7 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -648,12 +648,10 @@ function handleParagraphDeleteActions(
'affine:bookmark',
'affine:code',
'affine:image',
'affine:divider',
...EMBED_BLOCK_FLAVOUR_LIST,
])
) {
doc.deleteBlock(model, {
bringChildrenTo: parent,
});
const previousSiblingElement = getBlockComponentByModel(
editorHost,
previousSibling
Expand All @@ -664,6 +662,12 @@ function handleParagraphDeleteActions(
});
editorHost.selection.setGroup('note', [selection]);

if (model.text?.length === 0) {
doc.deleteBlock(model, {
bringChildrenTo: parent,
});
}

return true;
}

Expand Down Expand Up @@ -835,7 +839,16 @@ function handleParagraphBlockForwardDelete(
}
function handleEmbedDividerCodeSibling(nextSibling: ExtendedModel | null) {
if (matchFlavours(nextSibling, ['affine:divider'])) {
doc.deleteBlock(nextSibling);
const nextSiblingComponent = getBlockComponentByModel(
editorHost,
nextSibling
);
assertExists(nextSiblingComponent);
editorHost.selection.setGroup('note', [
editorHost.selection.create('block', {
path: nextSiblingComponent.path,
}),
]);
return true;
}

Expand Down
29 changes: 29 additions & 0 deletions packages/blocks/src/note-block/commands/focus-block-end.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { Command } from '@blocksuite/block-std';

export const focusBlockEnd: Command<'focusBlock'> = (ctx, next) => {
const { focusBlock, std } = ctx;
if (!focusBlock || !focusBlock.model.text) return;

const { selection } = std;

selection.setGroup('note', [
selection.create('text', {
from: {
path: focusBlock.path,
index: focusBlock.model.text.length,
length: 0,
},
to: null,
}),
]);

return next();
};

declare global {
namespace BlockSuite {
interface Commands {
focusBlockEnd: typeof focusBlockEnd;
}
}
}
25 changes: 25 additions & 0 deletions packages/blocks/src/note-block/commands/focus-block-start.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { Command } from '@blocksuite/block-std';

export const focusBlockStart: Command<'focusBlock'> = (ctx, next) => {
const { focusBlock, std } = ctx;
if (!focusBlock || !focusBlock.model.text) return;

const { selection } = std;

selection.setGroup('note', [
selection.create('text', {
from: { path: focusBlock.path, index: 0, length: 0 },
to: null,
}),
]);

return next();
};

declare global {
namespace BlockSuite {
interface Commands {
focusBlockStart: typeof focusBlockStart;
}
}
}
208 changes: 148 additions & 60 deletions packages/blocks/src/note-block/keymap-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type { ReactiveControllerHost } from 'lit';
import { moveBlockConfigs } from '../_common/configs/move-block.js';
import { quickActionConfig } from '../_common/configs/quick-action/config.js';
import { textConversionConfigs } from '../_common/configs/text-conversion.js';
import { buildPath } from '../_common/utils/index.js';
import { buildPath, matchFlavours } from '../_common/utils/index.js';
import { onModelElementUpdated } from '../root-block/utils/callback.js';
import { ensureBlockInContainer } from './utils.js';

Expand Down Expand Up @@ -67,94 +67,182 @@ export class KeymapController implements ReactiveController {
this._bindMoveBlockHotKey();
};

private _onArrowDown = () => {
private _onArrowDown = (ctx: UIEventStateContext) => {
const event = ctx.get('defaultState').event;

const [result] = this._std.command
.chain()
.inline((_, next) => {
this._reset();
return next();
})
.try(cmd => [
// text selection - select the next block
// 1. is paragraph, list, code block - follow the default behavior
// 2. is not - select the next block (use block selection instead of text selection)
cmd
.getTextSelection()
.inline<'currentSelectionPath'>((ctx, next) => {
const currentTextSelection = ctx.currentTextSelection;
assertExists(currentTextSelection);
return next({ currentSelectionPath: currentTextSelection.path });
})
.getNextBlock({
filter: block => ensureBlockInContainer(block, this.host),
})
.inline<'focusBlock'>((ctx, next) => {
const { nextBlock } = ctx;
assertExists(nextBlock);

if (
matchFlavours(nextBlock.model, [
'affine:paragraph',
'affine:list',
'affine:code',
])
)
return;

return next({
focusBlock: nextBlock,
});
})
.selectBlock(),
// block selection - select the next block
this._onBlockDown(cmd),
// 1. is paragraph, list, code block - focus it
// 2. is not - select it using block selection
cmd
.getBlockSelections()
.inline<'currentSelectionPath'>((ctx, next) => {
const currentBlockSelections = ctx.currentBlockSelections;
assertExists(currentBlockSelections);
const blockSelection = currentBlockSelections.at(-1);
if (!blockSelection) {
return;
}
return next({ currentSelectionPath: blockSelection.path });
})
.getNextBlock({
filter: block => ensureBlockInContainer(block, this.host),
})
.inline<'focusBlock'>((ctx, next) => {
const { nextBlock } = ctx;
assertExists(nextBlock);

if (
matchFlavours(nextBlock.model, [
'affine:paragraph',
'affine:list',
'affine:code',
])
) {
this._std.command
.chain()
.focusBlockStart({ focusBlock: nextBlock })
.run();
event.preventDefault();
return;
}

return next({
focusBlock: nextBlock,
});
})
.selectBlock(),
])
.run();

return result;
};

private _onBlockDown = (cmd: BlockSuite.CommandChain) => {
return cmd
.getBlockSelections()
.inline<'currentSelectionPath'>((ctx, next) => {
const currentBlockSelections = ctx.currentBlockSelections;
assertExists(currentBlockSelections);
const blockSelection = currentBlockSelections.at(-1);
if (!blockSelection) {
return;
}
return next({ currentSelectionPath: blockSelection.path });
})
.getNextBlock()
.inline<'focusBlock'>((ctx, next) => {
const { nextBlock } = ctx;
assertExists(nextBlock);

if (!ensureBlockInContainer(nextBlock, this.host)) {
return;
}

return next({
focusBlock: nextBlock,
});
})
.selectBlock();
};
private _onArrowUp = (ctx: UIEventStateContext) => {
const event = ctx.get('defaultState').event;

private _onArrowUp = () => {
const [result] = this._std.command
.chain()
.inline((_, next) => {
this._reset();
return next();
})
.try(cmd => [
// text selection - select the previous block
// 1. is paragraph, list, code block - follow the default behavior
// 2. is not - select the previous block (use block selection instead of text selection)
cmd
.getTextSelection()
.inline<'currentSelectionPath'>((ctx, next) => {
const currentTextSelection = ctx.currentTextSelection;
assertExists(currentTextSelection);
return next({ currentSelectionPath: currentTextSelection.path });
})
.getPrevBlock({
filter: block => ensureBlockInContainer(block, this.host),
})
.inline<'focusBlock'>((ctx, next) => {
const { prevBlock } = ctx;
assertExists(prevBlock);

if (
matchFlavours(prevBlock.model, [
'affine:paragraph',
'affine:list',
'affine:code',
])
)
return;

return next({
focusBlock: prevBlock,
});
})
.selectBlock(),
// block selection - select the previous block
this._onBlockUp(cmd),
// 1. is paragraph, list, code block - focus it
// 2. is not - select it using block selection
cmd
.getBlockSelections()
.inline<'currentSelectionPath'>((ctx, next) => {
const currentBlockSelections = ctx.currentBlockSelections;
assertExists(currentBlockSelections);
const blockSelection = currentBlockSelections.at(-1);
if (!blockSelection) {
return;
}
return next({ currentSelectionPath: blockSelection.path });
})
.getPrevBlock({
filter: block => ensureBlockInContainer(block, this.host),
})
.inline<'focusBlock'>((ctx, next) => {
const { prevBlock } = ctx;
assertExists(prevBlock);

if (
matchFlavours(prevBlock.model, [
'affine:paragraph',
'affine:list',
'affine:code',
])
) {
this._std.command
.chain()
.focusBlockEnd({ focusBlock: prevBlock })
.run();
event.preventDefault();
return;
}

return next({
focusBlock: prevBlock,
});
})
.selectBlock(),
])
.run();

return result;
};

private _onBlockUp = (cmd: BlockSuite.CommandChain) => {
return cmd
.getBlockSelections()
.inline<'currentSelectionPath'>((ctx, next) => {
const currentBlockSelections = ctx.currentBlockSelections;
assertExists(currentBlockSelections);
const blockSelection = currentBlockSelections.at(0);
if (!blockSelection) {
return;
}
return next({ currentSelectionPath: blockSelection.path });
})
.getPrevBlock()
.inline((ctx, next) => {
const { prevBlock } = ctx;
assertExists(prevBlock);

if (!ensureBlockInContainer(prevBlock, this.host)) {
return;
}

return next({
focusBlock: prevBlock,
});
})
.selectBlock();
};

private _onShiftArrowDown = () => {
const [result] = this._std.command
.chain()
Expand Down
4 changes: 4 additions & 0 deletions packages/blocks/src/note-block/note-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
captureEventTarget,
getDuplicateBlocks,
} from '../root-block/widgets/drag-handle/utils.js';
import { focusBlockEnd } from './commands/focus-block-end.js';
import { focusBlockStart } from './commands/focus-block-start.js';
import {
registerTextStyleCommands,
selectBlock,
Expand Down Expand Up @@ -131,6 +133,8 @@ export class NoteService extends BlockService<NoteBlockModel> {
this.std.command
.add('selectBlocksBetween', selectBlocksBetween)
.add('selectBlock', selectBlock)
.add('focusBlockStart', focusBlockStart)
.add('focusBlockEnd', focusBlockEnd)
.add('updateBlockType', updateBlockType);

registerTextStyleCommands(this.std);
Expand Down
Loading
Loading