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 -->
This commit is contained in:
L-Sun
2025-06-11 14:12:28 +08:00
committed by GitHub
parent c846c57a12
commit 24448659a4
33 changed files with 440 additions and 593 deletions
@@ -1,6 +1,5 @@
import { textKeymap } from '@blocksuite/affine-inline-preset';
import { ListBlockSchema } from '@blocksuite/affine-model';
import { markdownInput } from '@blocksuite/affine-rich-text';
import { getSelectedModelsCommand } from '@blocksuite/affine-shared/commands';
import { IS_MAC } from '@blocksuite/global/env';
import { KeymapExtension, TextSelection } from '@blocksuite/std';
@@ -125,20 +124,6 @@ export const ListKeymapExtension = KeymapExtension(
ctx.get('keyboardState').raw.preventDefault();
return true;
},
Space: ctx => {
if (!markdownInput(std)) {
return;
}
ctx.get('keyboardState').raw.preventDefault();
return true;
},
'Shift-Space': ctx => {
if (!markdownInput(std)) {
return;
}
ctx.get('keyboardState').raw.preventDefault();
return true;
},
};
},
{
@@ -0,0 +1,91 @@
import {
type ListBlockModel,
ListBlockSchema,
type ListType,
ParagraphBlockModel,
} from '@blocksuite/affine-model';
import { focusTextModel } from '@blocksuite/affine-rich-text';
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
import { matchModels, toNumberedList } from '@blocksuite/affine-shared/utils';
import type { BlockComponent } from '@blocksuite/std';
import { InlineMarkdownExtension } from '@blocksuite/std/inline';
export const ListMarkdownExtension =
InlineMarkdownExtension<AffineTextAttributes>({
name: 'list',
// group 2: number
// group 3: bullet
// group 4: bullet
// group 5: todo
// group 6: todo checked
pattern: /^((\d+\.)|(-)|(\*)|(\[ ?\])|(\[x\]))\s$/,
action: ({ inlineEditor, pattern, inlineRange, prefixText }) => {
if (inlineEditor.yTextString.slice(0, inlineRange.index).includes('\n')) {
return;
}
const match = prefixText.match(pattern);
if (!match) return;
let type: ListType;
if (match[2]) {
type = 'numbered';
} else if (match[3] || match[4]) {
type = 'bulleted';
} else if (match[5] || match[6]) {
type = 'todo';
} else {
return;
}
const checked = match[6] !== undefined;
if (!inlineEditor.rootElement) return;
const blockComponent =
inlineEditor.rootElement.closest<BlockComponent>('[data-block-id]');
if (!blockComponent) return;
const { model, std, store } = blockComponent;
if (!matchModels(model, [ParagraphBlockModel])) return;
if (type !== 'numbered') {
const parent = store.getParent(model);
if (!parent) return;
const index = parent.children.indexOf(model);
store.captureSync();
inlineEditor.deleteText({
index: 0,
length: inlineRange.index,
});
const id = store.addBlock<ListBlockModel>(
ListBlockSchema.model.flavour,
{
type: type,
text: model.text?.clone(),
children: model.children,
...(type === 'todo' ? { checked } : {}),
},
parent,
index
);
store.deleteBlock(model, { deleteChildren: false });
focusTextModel(std, id);
} else {
let order = parseInt(match[2]);
if (!Number.isInteger(order)) order = 1;
store.captureSync();
inlineEditor.deleteText({
index: 0,
length: inlineRange.index,
});
const id = toNumberedList(std, model, order);
if (!id) return;
focusTextModel(std, id);
}
},
});
@@ -7,6 +7,7 @@ import { literal } from 'lit/static-html.js';
import { effects } from './effects.js';
import { ListKeymapExtension, ListTextKeymapExtension } from './list-keymap.js';
import { ListMarkdownExtension } from './markdown.js';
export class ListViewExtension extends ViewExtensionProvider {
override name = 'affine-list-block';
@@ -23,6 +24,7 @@ export class ListViewExtension extends ViewExtensionProvider {
BlockViewExtension('affine:list', literal`affine-list`),
ListKeymapExtension,
ListTextKeymapExtension,
ListMarkdownExtension,
]);
}
}