Files
AFFiNE-Mirror/blocksuite/affine/blocks/list/src/list-keymap.ts
T
L-Sun 24448659a4 fix(editor): support markdown transform when using IME (#12778)
Fix #12284 
Close
[BS-3517](https://linear.app/affine-design/issue/BS-3517/微软新注音输入法无法使用markdown语法)

This PR refactor the markdown transform during inputting, including:
- Transfrom markdown syntax input in `inlineEditor.slots.inputting`,
where we can detect the space character inputed by IME like Microsoft
Bopomofo, but `keydown` event can't.
- Remove `markdown-input.ts` which was used in `KeymapExtension` of
paragraph, and refactor with `InlineMarkdownExtension`
- Adjust existing `InlineMarkdownExtension` since the space is included
in text.
- Add two `InlineMarkdownExtension` for paragraph and list to impl
Heading1-6, number, bullet, to-do list conversion.

Other changes:
- Improve type hint for parameter of `store.addBlock`

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Summary by CodeRabbit

- **New Features**
- Added markdown shortcuts for creating code blocks and dividers in the
rich text editor.
- Introduced enhanced paragraph markdown support for headings and
blockquotes with inline markdown patterns.
- Integrated new list markdown extension supporting numbered, bulleted,
and todo lists with checked states.

- **Improvements**
- Updated markdown formatting patterns to require trailing spaces for
links, LaTeX, and inline styles, improving detection accuracy.
- Markdown transformations now respond to input events instead of
keydown for smoother editing experience.
- Added focus management after markdown transformations to maintain
seamless editing flow.

- **Bug Fixes**
- Removed unnecessary prevention of default behavior on space and
shift-space key presses in list and paragraph editors.

- **Refactor**
- Enhanced event handling and typing for editor input events, improving
reliability and maintainability.
- Refined internal prefix text extraction logic for markdown processing.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-11 14:12:28 +08:00

137 lines
3.9 KiB
TypeScript

import { textKeymap } from '@blocksuite/affine-inline-preset';
import { ListBlockSchema } from '@blocksuite/affine-model';
import { getSelectedModelsCommand } from '@blocksuite/affine-shared/commands';
import { IS_MAC } from '@blocksuite/global/env';
import { KeymapExtension, TextSelection } from '@blocksuite/std';
import {
canDedentListCommand,
dedentListCommand,
} from './commands/dedent-list.js';
import {
canIndentListCommand,
indentListCommand,
} from './commands/indent-list.js';
import { listToParagraphCommand } from './commands/list-to-paragraph.js';
import { splitListCommand } from './commands/split-list.js';
import { forwardDelete } from './utils/forward-delete.js';
export const ListKeymapExtension = KeymapExtension(
std => {
return {
Enter: ctx => {
const text = std.selection.find(TextSelection);
if (!text) return false;
ctx.get('keyboardState').raw.preventDefault();
std.command
.chain()
.pipe(splitListCommand, {
blockId: text.from.blockId,
inlineIndex: text.from.index,
})
.run();
return true;
},
'Mod-Enter': ctx => {
const text = std.selection.find(TextSelection);
if (!text) return false;
ctx.get('keyboardState').raw.preventDefault();
std.command
.chain()
.pipe(splitListCommand, {
blockId: text.from.blockId,
inlineIndex: text.from.index,
})
.run();
return true;
},
Tab: ctx => {
const [_, { selectedModels }] = std.command
.chain()
.pipe(getSelectedModelsCommand, {
types: ['text'],
})
.run();
if (selectedModels?.length !== 1) {
return false;
}
const text = std.selection.find(TextSelection);
if (!text) return false;
ctx.get('keyboardState').raw.preventDefault();
std.command
.chain()
.pipe(canIndentListCommand, {
blockId: text.from.blockId,
inlineIndex: text.from.index,
})
.pipe(indentListCommand)
.run();
return true;
},
'Shift-Tab': ctx => {
const [_, { selectedModels }] = std.command
.chain()
.pipe(getSelectedModelsCommand, {
types: ['text'],
})
.run();
if (selectedModels?.length !== 1) {
return;
}
const text = std.selection.find(TextSelection);
if (!text) return false;
ctx.get('keyboardState').raw.preventDefault();
std.command
.chain()
.pipe(canDedentListCommand, {
blockId: text.from.blockId,
inlineIndex: text.from.index,
})
.pipe(dedentListCommand)
.run();
return true;
},
Backspace: ctx => {
const text = std.selection.find(TextSelection);
if (!text) return false;
const isCollapsed = text.isCollapsed();
const isStart = isCollapsed && text.from.index === 0;
if (!isStart) return false;
ctx.get('defaultState').event.preventDefault();
std.command
.chain()
.pipe(listToParagraphCommand, {
id: text.from.blockId,
})
.run();
return true;
},
'Control-d': ctx => {
if (!IS_MAC) return;
const deleted = forwardDelete(std);
if (!deleted) return;
ctx.get('keyboardState').raw.preventDefault();
return true;
},
Delete: ctx => {
const deleted = forwardDelete(std);
if (!deleted) return;
ctx.get('keyboardState').raw.preventDefault();
return true;
},
};
},
{
flavour: ListBlockSchema.model.flavour,
}
);
export const ListTextKeymapExtension = KeymapExtension(textKeymap, {
flavour: ListBlockSchema.model.flavour,
});