openSuggestionMenu: menu closes on the second typed character (single-transaction dispatch breaks queryStartPos)
还没有人认领这个 Issue。
评估
- 难度
- 2/5
- 预计耗时
- 1-3 小时
- 新手友好度
- 82/100
- Issue 类型
- 缺陷
- 描述清晰度
- 描述清楚
- 活跃度
- 活跃
- 技术栈
- react, typescript
- 领域
- frontend
调研方向
从 editor/BlockNoteEditor.ts 中的 openSuggestionMenu 开始,将其 transaction 与 SuggestionPlugin.handleTextInput 进行比较。阅读 api/positionMapping.ts 以了解 queryStartPos 的映射行为,然后验证拆分 insert 和 metadata 的 dispatch 是否能让菜单在多字符拉丁文和西里尔文查询时保持打开,并与输入触发器路径一致。
由索引模型根据 Issue 内容生成。
描述
Summary
A suggestion menu opened programmatically with editor.openSuggestionMenu(...) closes as soon as a second character is typed into it. The same menu opened by typing the trigger character filters normally. This makes openSuggestionMenu unusable for any programmatic entry point that expects the user to then type a query.
Version: @blocknote/core 0.33.x (React 18 line), reproduced in Chromium.
Impact
We hit this on the side-menu «+» button, which for us is the only way to reach the slash menu on a Russian ЙЦУКЕН keyboard layout — that layout has no / key at all, so «click +, then type» is the whole route. Every slash item is equally affected. It is not layout-specific though: it reproduces with plain Latin keys.
Reproduction
Open the slash menu programmatically and type subp one real keystroke at a time:
editor.setTextCursorPosition(someEmptyBlock);
editor.openSuggestionMenu("/", { deleteTriggerCharacter: true });
Observed, recording whether the menu is open after each keystroke together with the caret's text node:
s : open "/s"@2
u : CLOSED "/su"@3
b : CLOSED "/sub"@4
p : CLOSED "/subp"@5
Note the document is correct throughout — the trigger character is in the block and the caret is after the query. The menu is simply gone after the second character. Typing /subp by hand in the same editor filters correctly.
deleteTriggerCharacter is not the variable: without it the character is never inserted and it fails the same way; with it the document is right and it still fails.
Cause
openSuggestionMenu does the insert and the plugin meta in one transaction:
// editor/BlockNoteEditor.ts
this.focus();
this.transact((tr) => {
if (pluginState?.deleteTriggerCharacter) { tr.insertText(triggerCharacter); }
tr.scrollIntoView().setMeta(this.suggestionMenus.plugins[0], { … });
});
The native path in SuggestionPlugin.handleTextInput dispatches two:
view.dispatch(view.state.tr.insertText(text));
view.dispatch(view.state.tr.setMeta(suggestionMenuPluginKey, { triggerCharacter: snippet }).scrollIntoView());
That matters because the plugin's apply() builds queryStartPos inside the opening transaction's own apply:
const trackedPosition = trackPosition(
editor,
newState.selection.from - suggestionPluginTransactionMeta.triggerCharacter.length,
);
…
queryStartPos: () => trackedPosition() + triggerCharacter.length,
and trackPosition slices the editor's accumulated mapping at mapping.maps.length as read at that moment (api/positionMapping.ts). Whether the opening transaction's own map falls inside or outside that slice differs between the one-dispatch and two-dispatch shapes, so queryStartPos() drifts and the next keystroke fails the plugin's own guard:
newState.selection.from < prev.queryStartPos() ||
!newState.selection.$from.sameParent(newState.doc.resolve(prev.queryStartPos()))
The resulting plugin state is otherwise identical field for field between the two paths:
typed / (handleTextInput) |
openSuggestionMenu("/", { deleteTriggerCharacter: true }) |
|
|---|---|---|
triggerCharacter |
"/" |
"/" |
deleteTriggerCharacter after apply() |
meta key absent → undefined !== false → true |
true !== false → true |
ignoreQueryLength |
undefined (falsy) |
false (falsy) |
| trigger char in the document | yes | yes |
| menu survives 2nd keystroke | yes | no |
Confirmation
Replicating handleTextInput's two-transaction shape from application code fixes it completely — multi-character Latin and Cyrillic queries both filter, and the menu matches the typed-/ menu exactly:
const plugin = editor.suggestionMenus.plugins[0];
editor.transact((tr) => tr.insertText("/"));
editor.transact((tr) => tr.setMeta(plugin, { triggerCharacter: "/" }).scrollIntoView());
Forcing those two back into a single transaction reproduces the failure exactly, with everything else unchanged — that is the only variable.
Suggested fix
Split the dispatch inside openSuggestionMenu so the meta is set in a transaction after the insert, matching handleTextInput. Applications otherwise have to reach for editor.suggestionMenus.plugins[0] as a meta key to get a working programmatic entry point, which is what we have had to do.
Happy to open a PR if the approach looks right.
- 主要语言
- TypeScript
- 星标
- 10.2k
- 派生
- 772
- 平均合并
- 3 天 11 小时
- 30 天内合并 PR
- 17
贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
TypeCellOS/BlockNote 的其他 Issue
-
难度 2/5 1-3 小时 新手友好度 84/100
TypeCellOS/BlockNote#3098 ·
-
needs-triage
难度 2/5 1-3 小时 新手友好度 78/100
TypeCellOS/BlockNote#3073 ·
-
needs-triage
难度 2/5 1-3 小时 新手友好度 85/100
TypeCellOS/BlockNote#3072 ·
-
a11y
难度 2/5 1-3 小时 新手友好度 68/100
TypeCellOS/BlockNote#2855 ·
-
a11y
难度 2/5 1-3 小时 新手友好度 62/100
TypeCellOS/BlockNote#2829 · 1 条评论 ·
查看 TypeCellOS/BlockNote 的全部 Issue
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 84/100
Eynzof/Hermes-CN-Desktop#610 ·
-
bug clawsweeper:linked-pr-open clawsweeper:needs-live-repro clawsweeper:no-new-fix-pr impact:message-loss issue-rating: 🐚 platinum hermit P2 regression
难度 2/5 1-3 小时 新手友好度 78/100
-
enhancement
难度 2/5 1-3 小时 新手友好度 68/100
-
calcite-components needs triage refactor
难度 2/5 1-3 小时 新手友好度 75/100
Esri/calcite-design-system#15203 ·
-
难度 1/5 1 小时以内 新手友好度 78/100
fullcalendar/fullcalendar#8106 ·