fix(app): blocks from the / menu go into card fields

Inserting a sound, a YouTube video or a study button through the / menu
in a card's field failed (found with the Snap build). Two causes:

A card field closes its editor when the focus leaves it, except to a
dialog. The / menu takes the focus too, so the editor was gone while
its menu was open, and the block chosen went to an editor that no
longer existed. The field now stays open while the editor's own popup
holds the focus (the editor package's keepEditorFocusNotifier, which
its editors heed for themselves, read through
RichContentEditor.popupHasFocus).

The editor package keeps the menu, and the layer closing it on a tap,
above everything until the chosen item's handler returns, so a block
asking in a dialog (formula, video, study button) had the menu over
that dialog. Our items now close the menu first; the handler is given
the menu for that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-04 21:56:35 +02:00
commit 103881a28d
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -273,6 +273,51 @@ void main() {
expect(blocks, contains(containsPair('type', mathBlockType)));
});
testWidgets('the / menu takes the focus without closing its field, so the '
'block chosen goes into the card', (tester) async {
await cards.create('japanese', card('猫', 'cat'));
await showStudyset(tester, size: const Size(412, 915));
await open(tester, '猫');
await tester.tap(find.text('cat', findRichText: true));
await settle(tester);
final editor = tester.widget<AppFlowyEditor>(
find.descendant(
of: find.byType(RichContentEditor),
matching: find.byType(AppFlowyEditor),
),
);
final slash = editor.characterShortcutEvents.singleWhere(
(event) => event.character == '/',
);
await slash.handler(editor.editorState);
await tester.pumpAndSettle();
expect(find.byType(RichContentEditor), findsOneWidget);
// The menu builds only the items near what it shows.
final menu = find.descendant(
of: find.byType(SelectionMenuWidget),
matching: find.byType(Scrollable),
);
await tester.scrollUntilVisible(find.text('Formula'), 40, scrollable: menu);
await tester.ensureVisible(find.text('Formula'));
await tester.pumpAndSettle();
await tester.tap(find.text('Formula'));
await tester.pumpAndSettle();
expect(find.byType(SelectionMenuWidget), findsNothing);
await tester.enterText(find.byType(TextField), 'x^2');
await tester.tap(find.text('Save'));
await settle(tester);
await tester.tap(find.byType(BackButton));
await settle(tester);
final back = (await cards.cardsOf('japanese')).single.content.back;
final blocks =
(back.editorJson['document']! as Map<String, Object?>)['children']!
as List<Object?>;
expect(blocks, contains(containsPair('type', mathBlockType)));
});
testWidgets('the card types fit a phone, none of their names broken', (
tester,
) async {