relicense appflowy editor (#1938)

* revert:"fix: remove keyword when click selection menu item"

This reverts commit 5782dec45c.

* revert(appflowy_editor):revert "feat: double asterisks/underscores to bold text"

This reverts commit c0964fad5d.

* revert(appflowy_editor):revert "fix: workaround infinity formatting"

This reverts commit 6a902a2b21.
The Appflowy folder under the frontend had been removed before reverting.

* chore(appflow_editor):update test variable after reverting

* chore(appflowy_editor): comment out the test for reverting

* chore(appflowy_editor): update variable type after reverting

* chore(appflowy_editor): remove unused import after reverting

* feat(appflowy_editor): double asterisk to bold text

* test(appflowy_editor): test double asterisk to bold text

* fix(appflowy_editor): delete slash after a selection menu item is selected

* test(appflowy_editor): test selection menu widget after clicking

* feat(appflowy_editor):  double asterisk to bold text and remove slash after clicking selection menu item   (#1935)

* feat(appflowy_editor): double asterisk to bold text

* test(appflowy_editor): test double asterisk to bold text

* fix(appflowy_editor): delete slash after a selection menu item is selected

* test(appflowy_editor): test selection menu widget after clicking

* feat(appflowy_editor): double underscore to bold text

* test(appflowy_editor): test double underscore to bold text

* chore(appflowy_editor): put checkbox testing back

* chore: format code

---------

Co-authored-by: Yijing Huang <hyj891204@gmail.com>
This commit is contained in:
Lucas.Xu
2023-03-09 13:15:22 +07:00
committed by GitHub
parent 1d28bed281
commit 5e8f6a53a0
21 changed files with 396 additions and 493 deletions

View File

@ -7,7 +7,7 @@ import 'package:flowy_infra/image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
SelectionMenuItem boardMenuItem = SelectionMenuItem( SelectionMenuItem boardMenuItem = SelectionMenuItem(
name: () => LocaleKeys.document_plugins_referencedBoard.tr(), name: LocaleKeys.document_plugins_referencedBoard.tr(),
icon: (editorState, onSelected) { icon: (editorState, onSelected) {
return svgWidget( return svgWidget(
'editor/board', 'editor/board',

View File

@ -7,7 +7,7 @@ import 'package:flowy_infra/image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
SelectionMenuItem gridMenuItem = SelectionMenuItem( SelectionMenuItem gridMenuItem = SelectionMenuItem(
name: () => LocaleKeys.document_plugins_referencedGrid.tr(), name: LocaleKeys.document_plugins_referencedGrid.tr(),
icon: (editorState, onSelected) { icon: (editorState, onSelected) {
return svgWidget( return svgWidget(
'editor/grid', 'editor/grid',

View File

@ -37,7 +37,7 @@ ShortcutEventHandler _insertHorzaontalRule = (editorState, event) {
}; };
SelectionMenuItem horizontalRuleMenuItem = SelectionMenuItem( SelectionMenuItem horizontalRuleMenuItem = SelectionMenuItem(
name: () => 'Horizontal rule', name: 'Horizontal rule',
icon: (editorState, onSelected) => Icon( icon: (editorState, onSelected) => Icon(
Icons.horizontal_rule, Icons.horizontal_rule,
color: onSelected color: onSelected

View File

@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
SelectionMenuItem autoCompletionMenuItem = SelectionMenuItem( SelectionMenuItem autoCompletionMenuItem = SelectionMenuItem(
name: () => 'Auto generate content', name: 'Auto generate content',
icon: (editorState, onSelected) => Icon( icon: (editorState, onSelected) => Icon(
Icons.rocket, Icons.rocket,
size: 18.0, size: 18.0,

View File

@ -4,7 +4,7 @@ import 'package:example/plugin/AI/text_robot.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
SelectionMenuItem continueToWriteMenuItem = SelectionMenuItem( SelectionMenuItem continueToWriteMenuItem = SelectionMenuItem(
name: () => 'Continue To Write', name: 'Continue To Write',
icon: (editorState, onSelected) => Icon( icon: (editorState, onSelected) => Icon(
Icons.print, Icons.print,
size: 18.0, size: 18.0,

View File

@ -37,7 +37,6 @@ class BuiltInAttributeKey {
static String checkbox = 'checkbox'; static String checkbox = 'checkbox';
static String code = 'code'; static String code = 'code';
static String number = 'number'; static String number = 'number';
static String defaultFormating = 'defaultFormating';
static List<String> partialStyleKeys = [ static List<String> partialStyleKeys = [
BuiltInAttributeKey.bold, BuiltInAttributeKey.bold,

View File

@ -47,7 +47,7 @@ class _SelectionMenuItemWidgetState extends State<SelectionMenuItemWidget> {
: MaterialStateProperty.all(Colors.transparent), : MaterialStateProperty.all(Colors.transparent),
), ),
label: Text( label: Text(
widget.item.name(), widget.item.name,
textAlign: TextAlign.left, textAlign: TextAlign.left,
style: TextStyle( style: TextStyle(
color: (widget.isSelected || _onHover) color: (widget.isSelected || _onHover)

View File

@ -156,7 +156,7 @@ List<SelectionMenuItem> get defaultSelectionMenuItems =>
_defaultSelectionMenuItems; _defaultSelectionMenuItems;
final List<SelectionMenuItem> _defaultSelectionMenuItems = [ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
SelectionMenuItem( SelectionMenuItem(
name: () => AppFlowyEditorLocalizations.current.text, name: AppFlowyEditorLocalizations.current.text,
icon: (editorState, onSelected) => icon: (editorState, onSelected) =>
_selectionMenuIcon('text', editorState, onSelected), _selectionMenuIcon('text', editorState, onSelected),
keywords: ['text'], keywords: ['text'],
@ -165,7 +165,7 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
}, },
), ),
SelectionMenuItem( SelectionMenuItem(
name: () => AppFlowyEditorLocalizations.current.heading1, name: AppFlowyEditorLocalizations.current.heading1,
icon: (editorState, onSelected) => icon: (editorState, onSelected) =>
_selectionMenuIcon('h1', editorState, onSelected), _selectionMenuIcon('h1', editorState, onSelected),
keywords: ['heading 1, h1'], keywords: ['heading 1, h1'],
@ -174,7 +174,7 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
}, },
), ),
SelectionMenuItem( SelectionMenuItem(
name: () => AppFlowyEditorLocalizations.current.heading2, name: AppFlowyEditorLocalizations.current.heading2,
icon: (editorState, onSelected) => icon: (editorState, onSelected) =>
_selectionMenuIcon('h2', editorState, onSelected), _selectionMenuIcon('h2', editorState, onSelected),
keywords: ['heading 2, h2'], keywords: ['heading 2, h2'],
@ -183,7 +183,7 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
}, },
), ),
SelectionMenuItem( SelectionMenuItem(
name: () => AppFlowyEditorLocalizations.current.heading3, name: AppFlowyEditorLocalizations.current.heading3,
icon: (editorState, onSelected) => icon: (editorState, onSelected) =>
_selectionMenuIcon('h3', editorState, onSelected), _selectionMenuIcon('h3', editorState, onSelected),
keywords: ['heading 3, h3'], keywords: ['heading 3, h3'],
@ -192,14 +192,14 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
}, },
), ),
SelectionMenuItem( SelectionMenuItem(
name: () => AppFlowyEditorLocalizations.current.image, name: AppFlowyEditorLocalizations.current.image,
icon: (editorState, onSelected) => icon: (editorState, onSelected) =>
_selectionMenuIcon('image', editorState, onSelected), _selectionMenuIcon('image', editorState, onSelected),
keywords: ['image'], keywords: ['image'],
handler: showImageUploadMenu, handler: showImageUploadMenu,
), ),
SelectionMenuItem( SelectionMenuItem(
name: () => AppFlowyEditorLocalizations.current.bulletedList, name: AppFlowyEditorLocalizations.current.bulletedList,
icon: (editorState, onSelected) => icon: (editorState, onSelected) =>
_selectionMenuIcon('bulleted_list', editorState, onSelected), _selectionMenuIcon('bulleted_list', editorState, onSelected),
keywords: ['bulleted list', 'list', 'unordered list'], keywords: ['bulleted list', 'list', 'unordered list'],
@ -208,7 +208,7 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
}, },
), ),
SelectionMenuItem( SelectionMenuItem(
name: () => AppFlowyEditorLocalizations.current.numberedList, name: AppFlowyEditorLocalizations.current.numberedList,
icon: (editorState, onSelected) => icon: (editorState, onSelected) =>
_selectionMenuIcon('number', editorState, onSelected), _selectionMenuIcon('number', editorState, onSelected),
keywords: ['numbered list', 'list', 'ordered list'], keywords: ['numbered list', 'list', 'ordered list'],
@ -217,7 +217,7 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
}, },
), ),
SelectionMenuItem( SelectionMenuItem(
name: () => AppFlowyEditorLocalizations.current.checkbox, name: AppFlowyEditorLocalizations.current.checkbox,
icon: (editorState, onSelected) => icon: (editorState, onSelected) =>
_selectionMenuIcon('checkbox', editorState, onSelected), _selectionMenuIcon('checkbox', editorState, onSelected),
keywords: ['todo list', 'list', 'checkbox list'], keywords: ['todo list', 'list', 'checkbox list'],
@ -226,7 +226,7 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
}, },
), ),
SelectionMenuItem( SelectionMenuItem(
name: () => AppFlowyEditorLocalizations.current.quote, name: AppFlowyEditorLocalizations.current.quote,
icon: (editorState, onSelected) => icon: (editorState, onSelected) =>
_selectionMenuIcon('quote', editorState, onSelected), _selectionMenuIcon('quote', editorState, onSelected),
keywords: ['quote', 'refer'], keywords: ['quote', 'refer'],

View File

@ -20,14 +20,14 @@ class SelectionMenuItem {
required SelectionMenuItemHandler handler, required SelectionMenuItemHandler handler,
}) { }) {
this.handler = (editorState, menuService, context) { this.handler = (editorState, menuService, context) {
_deleteToSlash(editorState); _deleteSlash(editorState);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) { WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
handler(editorState, menuService, context); handler(editorState, menuService, context);
}); });
}; };
} }
final String Function() name; final String name;
final Widget Function(EditorState editorState, bool onSelected) icon; final Widget Function(EditorState editorState, bool onSelected) icon;
/// Customizes keywords for item. /// Customizes keywords for item.
@ -36,20 +36,23 @@ class SelectionMenuItem {
final List<String> keywords; final List<String> keywords;
late final SelectionMenuItemHandler handler; late final SelectionMenuItemHandler handler;
void _deleteToSlash(EditorState editorState) { void _deleteSlash(EditorState editorState) {
final selectionService = editorState.service.selectionService; final selectionService = editorState.service.selectionService;
final selection = selectionService.currentSelection.value; final selection = selectionService.currentSelection.value;
final nodes = selectionService.currentSelectedNodes; final nodes = selectionService.currentSelectedNodes;
if (selection != null && nodes.length == 1) { if (selection != null && nodes.length == 1) {
final node = nodes.first as TextNode; final node = nodes.first as TextNode;
final end = selection.start.offset; final end = selection.start.offset;
final start = node.toPlainText().substring(0, end).lastIndexOf('/'); final lastSlashIndex =
node.toPlainText().substring(0, end).lastIndexOf('/');
// delete all the texts after '/' along with '/'
final transaction = editorState.transaction final transaction = editorState.transaction
..deleteText( ..deleteText(
node, node,
start, lastSlashIndex,
selection.start.offset - start, end - lastSlashIndex,
); );
editorState.apply(transaction); editorState.apply(transaction);
} }
} }
@ -81,7 +84,7 @@ class SelectionMenuItem {
updateSelection, updateSelection,
}) { }) {
return SelectionMenuItem( return SelectionMenuItem(
name: () => name, name: name,
icon: (editorState, onSelected) => Icon( icon: (editorState, onSelected) => Icon(
iconData, iconData,
color: onSelected color: onSelected

View File

@ -265,135 +265,6 @@ ShortcutEventHandler markdownLinkOrImageHandler = (editorState, event) {
return KeyEventResult.handled; return KeyEventResult.handled;
}; };
// convert **abc** to bold abc.
ShortcutEventHandler doubleAsterisksToBold = (editorState, event) {
final selectionService = editorState.service.selectionService;
final selection = selectionService.currentSelection.value;
final textNodes = selectionService.currentSelectedNodes.whereType<TextNode>();
if (selection == null || !selection.isSingle || textNodes.length != 1) {
return KeyEventResult.ignored;
}
final textNode = textNodes.first;
final text = textNode.toPlainText().substring(0, selection.end.offset);
// make sure the last two characters are **.
if (text.length < 2 || text[selection.end.offset - 1] != '*') {
return KeyEventResult.ignored;
}
// find all the index of `*`.
final asteriskIndexes = <int>[];
for (var i = 0; i < text.length; i++) {
if (text[i] == '*') {
asteriskIndexes.add(i);
}
}
if (asteriskIndexes.length < 3) {
return KeyEventResult.ignored;
}
// make sure the second to last and third to last asterisks are connected.
final thirdToLastAsteriskIndex = asteriskIndexes[asteriskIndexes.length - 3];
final secondToLastAsteriskIndex = asteriskIndexes[asteriskIndexes.length - 2];
final lastAsterisIndex = asteriskIndexes[asteriskIndexes.length - 1];
if (secondToLastAsteriskIndex != thirdToLastAsteriskIndex + 1 ||
lastAsterisIndex == secondToLastAsteriskIndex + 1) {
return KeyEventResult.ignored;
}
// delete the last three asterisks.
// update the style of the text surround by `** **` to bold.
// and update the cursor position.
final transaction = editorState.transaction
..deleteText(textNode, lastAsterisIndex, 1)
..deleteText(textNode, thirdToLastAsteriskIndex, 2)
..formatText(
textNode,
thirdToLastAsteriskIndex,
selection.end.offset - thirdToLastAsteriskIndex - 3,
{
BuiltInAttributeKey.bold: true,
BuiltInAttributeKey.defaultFormating: true,
},
)
..afterSelection = Selection.collapsed(
Position(
path: textNode.path,
offset: selection.end.offset - 3,
),
);
editorState.apply(transaction);
return KeyEventResult.handled;
};
// convert __abc__ to bold abc.
ShortcutEventHandler doubleUnderscoresToBold = (editorState, event) {
final selectionService = editorState.service.selectionService;
final selection = selectionService.currentSelection.value;
final textNodes = selectionService.currentSelectedNodes.whereType<TextNode>();
if (selection == null || !selection.isSingle || textNodes.length != 1) {
return KeyEventResult.ignored;
}
final textNode = textNodes.first;
final text = textNode.toPlainText().substring(0, selection.end.offset);
// make sure the last two characters are __.
if (text.length < 2 || text[selection.end.offset - 1] != '_') {
return KeyEventResult.ignored;
}
// find all the index of `_`.
final underscoreIndexes = <int>[];
for (var i = 0; i < text.length; i++) {
if (text[i] == '_') {
underscoreIndexes.add(i);
}
}
if (underscoreIndexes.length < 3) {
return KeyEventResult.ignored;
}
// make sure the second to last and third to last underscores are connected.
final thirdToLastUnderscoreIndex =
underscoreIndexes[underscoreIndexes.length - 3];
final secondToLastUnderscoreIndex =
underscoreIndexes[underscoreIndexes.length - 2];
final lastAsterisIndex = underscoreIndexes[underscoreIndexes.length - 1];
if (secondToLastUnderscoreIndex != thirdToLastUnderscoreIndex + 1 ||
lastAsterisIndex == secondToLastUnderscoreIndex + 1) {
return KeyEventResult.ignored;
}
// delete the last three underscores.
// update the style of the text surround by `__ __` to bold.
// and update the cursor position.
final transaction = editorState.transaction
..deleteText(textNode, lastAsterisIndex, 1)
..deleteText(textNode, thirdToLastUnderscoreIndex, 2)
..formatText(
textNode,
thirdToLastUnderscoreIndex,
selection.end.offset - thirdToLastUnderscoreIndex - 3,
{
BuiltInAttributeKey.bold: true,
BuiltInAttributeKey.defaultFormating: true,
},
)
..afterSelection = Selection.collapsed(
Position(
path: textNode.path,
offset: selection.end.offset - 3,
),
);
editorState.apply(transaction);
return KeyEventResult.handled;
};
ShortcutEventHandler underscoreToItalicHandler = (editorState, event) { ShortcutEventHandler underscoreToItalicHandler = (editorState, event) {
// Obtain the selection and selected nodes of the current document through the 'selectionService' // Obtain the selection and selected nodes of the current document through the 'selectionService'
// to determine whether the selection is collapsed and whether the selected node is a text node. // to determine whether the selection is collapsed and whether the selected node is a text node.
@ -438,3 +309,117 @@ ShortcutEventHandler underscoreToItalicHandler = (editorState, event) {
return KeyEventResult.handled; return KeyEventResult.handled;
}; };
ShortcutEventHandler doubleAsteriskToBoldHanlder = (editorState, event) {
final selectionService = editorState.service.selectionService;
final selection = selectionService.currentSelection.value;
final textNodes = selectionService.currentSelectedNodes.whereType<TextNode>();
if (selection == null || !selection.isSingle || textNodes.length != 1) {
return KeyEventResult.ignored;
}
final textNode = textNodes.first;
final text = textNode.toPlainText();
// make sure the last two characters are '**'
if (text.length < 2 || text[selection.end.offset - 1] != '*') {
return KeyEventResult.ignored;
}
// find all the index of '*'
final asteriskIndexList = <int>[];
for (var i = 0; i < text.length; i++) {
if (text[i] == '*') {
asteriskIndexList.add(i);
}
}
if (asteriskIndexList.length < 3) return KeyEventResult.ignored;
// make sure the second to last and third to last asterisk are connected
final thirdToLastAsteriskIndex =
asteriskIndexList[asteriskIndexList.length - 3];
final secondToLastAsteriskIndex =
asteriskIndexList[asteriskIndexList.length - 2];
final lastAsteriskIndex = asteriskIndexList[asteriskIndexList.length - 1];
if (secondToLastAsteriskIndex != thirdToLastAsteriskIndex + 1 ||
lastAsteriskIndex == secondToLastAsteriskIndex + 1) {
return KeyEventResult.ignored;
}
//delete the last three asterisks
//update the style of the text surround by '** **' to bold
//update the cursor position
final transaction = editorState.transaction
..deleteText(textNode, lastAsteriskIndex, 1)
..deleteText(textNode, thirdToLastAsteriskIndex, 2)
..formatText(textNode, thirdToLastAsteriskIndex,
selection.end.offset - thirdToLastAsteriskIndex - 2, {
BuiltInAttributeKey.bold: true,
})
..afterSelection = Selection.collapsed(
Position(path: textNode.path, offset: selection.end.offset - 3));
editorState.apply(transaction);
return KeyEventResult.handled;
};
//Implement in the same way as doubleAsteriskToBoldHanlder
ShortcutEventHandler doubleUnderscoreToBoldHanlder = (editorState, event) {
final selectionService = editorState.service.selectionService;
final selection = selectionService.currentSelection.value;
final textNodes = selectionService.currentSelectedNodes.whereType<TextNode>();
if (selection == null || !selection.isSingle || textNodes.length != 1) {
return KeyEventResult.ignored;
}
final textNode = textNodes.first;
final text = textNode.toPlainText();
// make sure the last two characters are '__'
if (text.length < 2 || text[selection.end.offset - 1] != '_') {
return KeyEventResult.ignored;
}
// find all the index of '_'
final underscoreIndexList = <int>[];
for (var i = 0; i < text.length; i++) {
if (text[i] == '_') {
underscoreIndexList.add(i);
}
}
if (underscoreIndexList.length < 3) return KeyEventResult.ignored;
// make sure the second to last and third to last underscore are connected
final thirdToLastUnderscoreIndex =
underscoreIndexList[underscoreIndexList.length - 3];
final secondToLastUnderscoreIndex =
underscoreIndexList[underscoreIndexList.length - 2];
final lastUnderscoreIndex =
underscoreIndexList[underscoreIndexList.length - 1];
if (secondToLastUnderscoreIndex != thirdToLastUnderscoreIndex + 1 ||
lastUnderscoreIndex == secondToLastUnderscoreIndex + 1) {
return KeyEventResult.ignored;
}
//delete the last three underscores
//update the style of the text surround by '__ __' to bold
//update the cursor position
final transaction = editorState.transaction
..deleteText(textNode, lastUnderscoreIndex, 1)
..deleteText(textNode, thirdToLastUnderscoreIndex, 2)
..formatText(textNode, thirdToLastUnderscoreIndex,
selection.end.offset - thirdToLastUnderscoreIndex - 2, {
BuiltInAttributeKey.bold: true,
})
..afterSelection = Selection.collapsed(
Position(path: textNode.path, offset: selection.end.offset - 3));
editorState.apply(transaction);
return KeyEventResult.handled;
};

View File

@ -1,5 +1,3 @@
// List<>
import 'package:appflowy_editor/src/service/internal_key_event_handlers/arrow_keys_handler.dart'; import 'package:appflowy_editor/src/service/internal_key_event_handlers/arrow_keys_handler.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/backspace_handler.dart'; import 'package:appflowy_editor/src/service/internal_key_event_handlers/backspace_handler.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/copy_paste_handler.dart'; import 'package:appflowy_editor/src/service/internal_key_event_handlers/copy_paste_handler.dart';
@ -284,16 +282,6 @@ List<ShortcutEvent> builtInShortcutEvents = [
command: 'tab', command: 'tab',
handler: tabHandler, handler: tabHandler,
), ),
ShortcutEvent(
key: 'Double stars to bold',
command: 'shift+asterisk',
handler: doubleAsterisksToBold,
),
ShortcutEvent(
key: 'Double underscores to bold',
command: 'shift+underscore',
handler: doubleUnderscoresToBold,
),
ShortcutEvent( ShortcutEvent(
key: 'Backquote to code', key: 'Backquote to code',
command: 'backquote', command: 'backquote',
@ -319,6 +307,16 @@ List<ShortcutEvent> builtInShortcutEvents = [
command: 'shift+underscore', command: 'shift+underscore',
handler: underscoreToItalicHandler, handler: underscoreToItalicHandler,
), ),
ShortcutEvent(
key: 'Double asterisk to bold',
command: 'shift+digit 8',
handler: doubleAsteriskToBoldHanlder,
),
ShortcutEvent(
key: 'Double underscore to bold',
command: 'shift+underscore',
handler: doubleUnderscoreToBoldHanlder,
),
// https://github.com/flutter/flutter/issues/104944 // https://github.com/flutter/flutter/issues/104944
// Workaround: Using space editing on the web platform often results in errors, // Workaround: Using space editing on the web platform often results in errors,
// so adding a shortcut event to handle the space input instead of using the // so adding a shortcut event to handle the space input instead of using the

View File

@ -142,15 +142,15 @@ extension on LogicalKeyboardKey {
if (this == LogicalKeyboardKey.keyZ) { if (this == LogicalKeyboardKey.keyZ) {
return PhysicalKeyboardKey.keyZ; return PhysicalKeyboardKey.keyZ;
} }
if (this == LogicalKeyboardKey.asterisk) { if (this == LogicalKeyboardKey.tilde) {
return PhysicalKeyboardKey.backquote;
}
if (this == LogicalKeyboardKey.digit8) {
return PhysicalKeyboardKey.digit8; return PhysicalKeyboardKey.digit8;
} }
if (this == LogicalKeyboardKey.underscore) { if (this == LogicalKeyboardKey.underscore) {
return PhysicalKeyboardKey.minus; return PhysicalKeyboardKey.minus;
} }
if (this == LogicalKeyboardKey.tilde) {
return PhysicalKeyboardKey.backquote;
}
throw UnimplementedError(); throw UnimplementedError();
} }
} }

View File

@ -70,7 +70,7 @@ void main() async {
}); });
// https://github.com/AppFlowy-IO/AppFlowy/issues/1763 // https://github.com/AppFlowy-IO/AppFlowy/issues/1763
// [Bug] Mouse unable to click a certain area #1763 // // [Bug] Mouse unable to click a certain area #1763
testWidgets('insert a new checkbox after an exsiting checkbox', testWidgets('insert a new checkbox after an exsiting checkbox',
(tester) async { (tester) async {
// Before // Before

View File

@ -10,40 +10,44 @@ void main() async {
}); });
group('selection_menu_widget.dart', () { group('selection_menu_widget.dart', () {
for (var i = 0; i < defaultSelectionMenuItems.length; i += 1) { // const i = defaultSelectionMenuItems.length;
testWidgets('Selects number.$i item in selection menu with enter', //
(tester) async { // Because the `defaultSelectionMenuItems` uses localization,
final editor = await _prepare(tester); // and the MaterialApp has not been initialized at the time of getting the value,
for (var j = 0; j < i; j++) { // it will crash.
await editor.pressLogicKey(LogicalKeyboardKey.arrowDown); //
} // Use const value temporarily instead.
const i = 7;
testWidgets('Selects number.$i item in selection menu with keyboard',
(tester) async {
final editor = await _prepare(tester);
for (var j = 0; j < i; j++) {
await editor.pressLogicKey(LogicalKeyboardKey.arrowDown);
}
await editor.pressLogicKey(LogicalKeyboardKey.enter); await editor.pressLogicKey(LogicalKeyboardKey.enter);
expect( expect(
find.byType(SelectionMenuWidget, skipOffstage: false), find.byType(SelectionMenuWidget, skipOffstage: false),
findsNothing, findsNothing,
); );
if (defaultSelectionMenuItems[i].name() != 'Image') { if (defaultSelectionMenuItems[i].name != 'Image') {
await _testDefaultSelectionMenuItems(i, editor); await _testDefaultSelectionMenuItems(i, editor);
} }
}); });
testWidgets('Selects number.$i item in selection menu with click', testWidgets('Selects number.$i item in selection menu with clicking',
(tester) async { (tester) async {
final editor = await _prepare(tester); final editor = await _prepare(tester);
await tester.tap(find.byType(SelectionMenuItemWidget).at(i));
await tester.tap(find.byType(SelectionMenuItemWidget).at(i)); await tester.pumpAndSettle();
await tester.pumpAndSettle(); expect(
find.byType(SelectionMenuWidget, skipOffstage: false),
expect( findsNothing,
find.byType(SelectionMenuWidget, skipOffstage: false), );
findsNothing, if (defaultSelectionMenuItems[i].name != 'Image') {
); await _testDefaultSelectionMenuItems(i, editor);
if (defaultSelectionMenuItems[i].name() != 'Image') { }
await _testDefaultSelectionMenuItems(i, editor); });
}
});
}
testWidgets('Search item in selection menu util no results', testWidgets('Search item in selection menu util no results',
(tester) async { (tester) async {
@ -136,7 +140,7 @@ Future<EditorWidgetTester> _prepare(WidgetTester tester) async {
); );
for (final item in defaultSelectionMenuItems) { for (final item in defaultSelectionMenuItems) {
expect(find.text(item.name()), findsOneWidget); expect(find.text(item.name), findsOneWidget);
} }
return Future.value(editor); return Future.value(editor);
@ -146,28 +150,31 @@ Future<void> _testDefaultSelectionMenuItems(
int index, EditorWidgetTester editor) async { int index, EditorWidgetTester editor) async {
expect(editor.documentLength, 4); expect(editor.documentLength, 4);
expect(editor.documentSelection, Selection.single(path: [2], startOffset: 0)); expect(editor.documentSelection, Selection.single(path: [2], startOffset: 0));
expect((editor.nodeAtPath([0]) as TextNode).toPlainText(),
'Welcome to Appflowy 😁');
expect((editor.nodeAtPath([1]) as TextNode).toPlainText(), expect((editor.nodeAtPath([1]) as TextNode).toPlainText(),
'Welcome to Appflowy 😁'); 'Welcome to Appflowy 😁');
final node = editor.nodeAtPath([2]); final node = editor.nodeAtPath([2]);
final item = defaultSelectionMenuItems[index]; final item = defaultSelectionMenuItems[index];
final itemName = item.name(); if (item.name == 'Text') {
if (itemName == 'Text') {
expect(node?.subtype == null, true); expect(node?.subtype == null, true);
} else if (itemName == 'Heading 1') { expect(node?.toString(), null);
} else if (item.name == 'Heading 1') {
expect(node?.subtype, BuiltInAttributeKey.heading); expect(node?.subtype, BuiltInAttributeKey.heading);
expect(node?.attributes.heading, BuiltInAttributeKey.h1); expect(node?.attributes.heading, BuiltInAttributeKey.h1);
} else if (itemName == 'Heading 2') { expect(node?.toString(), null);
} else if (item.name == 'Heading 2') {
expect(node?.subtype, BuiltInAttributeKey.heading); expect(node?.subtype, BuiltInAttributeKey.heading);
expect(node?.attributes.heading, BuiltInAttributeKey.h2); expect(node?.attributes.heading, BuiltInAttributeKey.h2);
} else if (itemName == 'Heading 3') { expect(node?.toString(), null);
} else if (item.name == 'Heading 3') {
expect(node?.subtype, BuiltInAttributeKey.heading); expect(node?.subtype, BuiltInAttributeKey.heading);
expect(node?.attributes.heading, BuiltInAttributeKey.h3); expect(node?.attributes.heading, BuiltInAttributeKey.h3);
} else if (itemName == 'Bulleted list') { expect(node?.toString(), null);
} else if (item.name == 'Bulleted list') {
expect(node?.subtype, BuiltInAttributeKey.bulletedList); expect(node?.subtype, BuiltInAttributeKey.bulletedList);
} else if (itemName == 'Checkbox') { } else if (item.name == 'Checkbox') {
expect(node?.subtype, BuiltInAttributeKey.checkbox); expect(node?.subtype, BuiltInAttributeKey.checkbox);
expect(node?.attributes.check, false); expect(node?.attributes.check, false);
} else if (itemName == 'Quote') {
expect(node?.subtype, BuiltInAttributeKey.quote);
} }
} }

View File

@ -1,277 +0,0 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/extensions/text_node_extensions.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import '../../infra/test_editor.dart';
void main() async {
setUpAll(() {
TestWidgetsFlutterBinding.ensureInitialized();
});
group('markdown_syntax_to_styled_text_handler.dart', () {
group('convert double asterisks to bold', () {
Future<void> insertAsterisk(
EditorWidgetTester editor, {
int repeat = 1,
}) async {
for (var i = 0; i < repeat; i++) {
await editor.pressLogicKey(
LogicalKeyboardKey.asterisk,
isShiftPressed: true,
);
}
}
testWidgets('**AppFlowy** to bold AppFlowy', (tester) async {
const text = '**AppFlowy*';
final editor = tester.editor..insertTextNode('');
await editor.startTesting();
await editor.updateSelection(
Selection.single(path: [0], startOffset: 0),
);
final textNode = editor.nodeAtPath([0]) as TextNode;
for (var i = 0; i < text.length; i++) {
await editor.insertText(textNode, text[i], i);
}
await insertAsterisk(editor);
final allBold = textNode.allSatisfyBoldInSelection(
Selection.single(
path: [0],
startOffset: 0,
endOffset: textNode.toPlainText().length,
),
);
expect(allBold, true);
expect(textNode.toPlainText(), 'AppFlowy');
});
testWidgets('App**Flowy** to bold AppFlowy', (tester) async {
const text = 'App**Flowy*';
final editor = tester.editor..insertTextNode('');
await editor.startTesting();
await editor.updateSelection(
Selection.single(path: [0], startOffset: 0),
);
final textNode = editor.nodeAtPath([0]) as TextNode;
for (var i = 0; i < text.length; i++) {
await editor.insertText(textNode, text[i], i);
}
await insertAsterisk(editor);
final allBold = textNode.allSatisfyBoldInSelection(
Selection.single(
path: [0],
startOffset: 3,
endOffset: textNode.toPlainText().length,
),
);
expect(allBold, true);
expect(textNode.toPlainText(), 'AppFlowy');
});
testWidgets('***AppFlowy** to bold *AppFlowy', (tester) async {
const text = '***AppFlowy*';
final editor = tester.editor..insertTextNode('');
await editor.startTesting();
await editor.updateSelection(
Selection.single(path: [0], startOffset: 0),
);
final textNode = editor.nodeAtPath([0]) as TextNode;
for (var i = 0; i < text.length; i++) {
await editor.insertText(textNode, text[i], i);
}
await insertAsterisk(editor);
final allBold = textNode.allSatisfyBoldInSelection(
Selection.single(
path: [0],
startOffset: 1,
endOffset: textNode.toPlainText().length,
),
);
expect(allBold, true);
expect(textNode.toPlainText(), '*AppFlowy');
});
testWidgets('**AppFlowy** application to bold AppFlowy only',
(tester) async {
const boldText = '**AppFlowy*';
final editor = tester.editor..insertTextNode('');
await editor.startTesting();
await editor.updateSelection(
Selection.single(path: [0], startOffset: 0),
);
final textNode = editor.nodeAtPath([0]) as TextNode;
for (var i = 0; i < boldText.length; i++) {
await editor.insertText(textNode, boldText[i], i);
}
await insertAsterisk(editor);
final boldTextLength = boldText.replaceAll('*', '').length;
final appFlowyBold = textNode.allSatisfyBoldInSelection(
Selection.single(
path: [0],
startOffset: 0,
endOffset: boldTextLength,
),
);
expect(appFlowyBold, true);
expect(textNode.toPlainText(), 'AppFlowy');
});
testWidgets('**** nothing changes', (tester) async {
const text = '***';
final editor = tester.editor..insertTextNode('');
await editor.startTesting();
await editor.updateSelection(
Selection.single(path: [0], startOffset: 0),
);
final textNode = editor.nodeAtPath([0]) as TextNode;
for (var i = 0; i < text.length; i++) {
await editor.insertText(textNode, text[i], i);
}
await insertAsterisk(editor);
final allBold = textNode.allSatisfyBoldInSelection(
Selection.single(
path: [0],
startOffset: 0,
endOffset: textNode.toPlainText().length,
),
);
expect(allBold, false);
expect(textNode.toPlainText(), text);
});
});
group('convert double underscores to bold', () {
Future<void> insertUnderscore(
EditorWidgetTester editor, {
int repeat = 1,
}) async {
for (var i = 0; i < repeat; i++) {
await editor.pressLogicKey(
LogicalKeyboardKey.underscore,
isShiftPressed: true,
);
}
}
testWidgets('__AppFlowy__ to bold AppFlowy', (tester) async {
const text = '__AppFlowy_';
final editor = tester.editor..insertTextNode('');
await editor.startTesting();
await editor.updateSelection(
Selection.single(path: [0], startOffset: 0),
);
final textNode = editor.nodeAtPath([0]) as TextNode;
for (var i = 0; i < text.length; i++) {
await editor.insertText(textNode, text[i], i);
}
await insertUnderscore(editor);
final allBold = textNode.allSatisfyBoldInSelection(
Selection.single(
path: [0],
startOffset: 0,
endOffset: textNode.toPlainText().length,
),
);
expect(allBold, true);
expect(textNode.toPlainText(), 'AppFlowy');
});
testWidgets('App__Flowy__ to bold AppFlowy', (tester) async {
const text = 'App__Flowy_';
final editor = tester.editor..insertTextNode('');
await editor.startTesting();
await editor.updateSelection(
Selection.single(path: [0], startOffset: 0),
);
final textNode = editor.nodeAtPath([0]) as TextNode;
for (var i = 0; i < text.length; i++) {
await editor.insertText(textNode, text[i], i);
}
await insertUnderscore(editor);
final allBold = textNode.allSatisfyBoldInSelection(
Selection.single(
path: [0],
startOffset: 3,
endOffset: textNode.toPlainText().length,
),
);
expect(allBold, true);
expect(textNode.toPlainText(), 'AppFlowy');
});
testWidgets('___AppFlowy__ to bold _AppFlowy', (tester) async {
const text = '___AppFlowy_';
final editor = tester.editor..insertTextNode('');
await editor.startTesting();
await editor.updateSelection(
Selection.single(path: [0], startOffset: 0),
);
final textNode = editor.nodeAtPath([0]) as TextNode;
for (var i = 0; i < text.length; i++) {
await editor.insertText(textNode, text[i], i);
}
await insertUnderscore(editor);
final allBold = textNode.allSatisfyBoldInSelection(
Selection.single(
path: [0],
startOffset: 1,
endOffset: textNode.toPlainText().length,
),
);
expect(allBold, true);
expect(textNode.toPlainText(), '_AppFlowy');
});
testWidgets('__AppFlowy__ application to bold AppFlowy only',
(tester) async {
const boldText = '__AppFlowy_';
final editor = tester.editor..insertTextNode('');
await editor.startTesting();
await editor.updateSelection(
Selection.single(path: [0], startOffset: 0),
);
final textNode = editor.nodeAtPath([0]) as TextNode;
for (var i = 0; i < boldText.length; i++) {
await editor.insertText(textNode, boldText[i], i);
}
await insertUnderscore(editor);
final boldTextLength = boldText.replaceAll('_', '').length;
final appFlowyBold = textNode.allSatisfyBoldInSelection(
Selection.single(
path: [0],
startOffset: 0,
endOffset: boldTextLength,
),
);
expect(appFlowyBold, true);
expect(textNode.toPlainText(), 'AppFlowy');
});
testWidgets('____ nothing changes', (tester) async {
const text = '___';
final editor = tester.editor..insertTextNode('');
await editor.startTesting();
await editor.updateSelection(
Selection.single(path: [0], startOffset: 0),
);
final textNode = editor.nodeAtPath([0]) as TextNode;
for (var i = 0; i < text.length; i++) {
await editor.insertText(textNode, text[i], i);
}
await insertUnderscore(editor);
final allBold = textNode.allSatisfyBoldInSelection(
Selection.single(
path: [0],
startOffset: 0,
endOffset: textNode.toPlainText().length,
),
);
expect(allBold, false);
expect(textNode.toPlainText(), text);
});
});
});
}

View File

@ -257,4 +257,192 @@ void main() async {
}); });
}); });
}); });
group('convert double asterisk to bold', () {
Future<void> insertAsterisk(
EditorWidgetTester editor, {
int repeat = 1,
}) async {
for (var i = 0; i < repeat; i++) {
await editor.pressLogicKey(
LogicalKeyboardKey.digit8,
isShiftPressed: true,
);
}
}
testWidgets('**AppFlowy** to bold AppFlowy', ((widgetTester) async {
const text = '**AppFlowy*';
final editor = widgetTester.editor..insertTextNode('');
await editor.startTesting();
await editor.updateSelection(Selection.single(path: [0], startOffset: 0));
final textNode = editor.nodeAtPath([0]) as TextNode;
for (var i = 0; i < text.length; i++) {
await editor.insertText(textNode, text[i], i);
}
await insertAsterisk(editor);
final allBold = textNode.allSatisfyBoldInSelection(Selection.single(
path: [0], startOffset: 0, endOffset: textNode.toPlainText().length));
expect(allBold, true);
expect(textNode.toPlainText(), 'AppFlowy');
}));
testWidgets('App**Flowy** to bold AppFlowy', ((widgetTester) async {
const text = 'App**Flowy*';
final editor = widgetTester.editor..insertTextNode('');
await editor.startTesting();
await editor.updateSelection(Selection.single(path: [0], startOffset: 0));
final textNode = editor.nodeAtPath([0]) as TextNode;
for (var i = 0; i < text.length; i++) {
await editor.insertText(textNode, text[i], i);
}
await insertAsterisk(editor);
final allBold = textNode.allSatisfyBoldInSelection(Selection.single(
path: [0], startOffset: 3, endOffset: textNode.toPlainText().length));
expect(allBold, true);
expect(textNode.toPlainText(), 'AppFlowy');
}));
testWidgets('***AppFlowy** to bold *AppFlowy', ((widgetTester) async {
const text = '***AppFlowy*';
final editor = widgetTester.editor..insertTextNode('');
await editor.startTesting();
await editor.updateSelection(Selection.single(path: [0], startOffset: 0));
final textNode = editor.nodeAtPath([0]) as TextNode;
for (var i = 0; i < text.length; i++) {
await editor.insertText(textNode, text[i], i);
}
await insertAsterisk(editor);
final allBold = textNode.allSatisfyBoldInSelection(Selection.single(
path: [0], startOffset: 1, endOffset: textNode.toPlainText().length));
expect(allBold, true);
expect(textNode.toPlainText(), '*AppFlowy');
}));
testWidgets('**** nothing changes', ((widgetTester) async {
const text = '***';
final editor = widgetTester.editor..insertTextNode('');
await editor.startTesting();
await editor.updateSelection(Selection.single(path: [0], startOffset: 0));
final textNode = editor.nodeAtPath([0]) as TextNode;
for (var i = 0; i < text.length; i++) {
await editor.insertText(textNode, text[i], i);
}
await insertAsterisk(editor);
final allBold = textNode.allSatisfyBoldInSelection(Selection.single(
path: [0], startOffset: 0, endOffset: textNode.toPlainText().length));
expect(allBold, false);
expect(textNode.toPlainText(), text);
}));
});
group('convert double underscore to bold', () {
Future<void> insertUnderscore(
EditorWidgetTester editor, {
int repeat = 1,
}) async {
for (var i = 0; i < repeat; i++) {
await editor.pressLogicKey(
LogicalKeyboardKey.underscore,
isShiftPressed: true,
);
}
}
testWidgets('__AppFlowy__ to bold AppFlowy', ((widgetTester) async {
const text = '__AppFlowy_';
final editor = widgetTester.editor..insertTextNode('');
await editor.startTesting();
await editor.updateSelection(Selection.single(path: [0], startOffset: 0));
final textNode = editor.nodeAtPath([0]) as TextNode;
for (var i = 0; i < text.length; i++) {
await editor.insertText(textNode, text[i], i);
}
await insertUnderscore(editor);
final allBold = textNode.allSatisfyBoldInSelection(Selection.single(
path: [0], startOffset: 0, endOffset: textNode.toPlainText().length));
expect(allBold, true);
expect(textNode.toPlainText(), 'AppFlowy');
}));
testWidgets('App__Flowy__ to bold AppFlowy', ((widgetTester) async {
const text = 'App__Flowy_';
final editor = widgetTester.editor..insertTextNode('');
await editor.startTesting();
await editor.updateSelection(Selection.single(path: [0], startOffset: 0));
final textNode = editor.nodeAtPath([0]) as TextNode;
for (var i = 0; i < text.length; i++) {
await editor.insertText(textNode, text[i], i);
}
await insertUnderscore(editor);
final allBold = textNode.allSatisfyBoldInSelection(Selection.single(
path: [0], startOffset: 3, endOffset: textNode.toPlainText().length));
expect(allBold, true);
expect(textNode.toPlainText(), 'AppFlowy');
}));
testWidgets('__*AppFlowy__ to bold *AppFlowy', ((widgetTester) async {
const text = '__*AppFlowy_';
final editor = widgetTester.editor..insertTextNode('');
await editor.startTesting();
await editor.updateSelection(Selection.single(path: [0], startOffset: 0));
final textNode = editor.nodeAtPath([0]) as TextNode;
for (var i = 0; i < text.length; i++) {
await editor.insertText(textNode, text[i], i);
}
await insertUnderscore(editor);
final allBold = textNode.allSatisfyBoldInSelection(Selection.single(
path: [0], startOffset: 1, endOffset: textNode.toPlainText().length));
expect(allBold, true);
expect(textNode.toPlainText(), '*AppFlowy');
}));
testWidgets('____ nothing changes', ((widgetTester) async {
const text = '___';
final editor = widgetTester.editor..insertTextNode('');
await editor.startTesting();
await editor.updateSelection(Selection.single(path: [0], startOffset: 0));
final textNode = editor.nodeAtPath([0]) as TextNode;
for (var i = 0; i < text.length; i++) {
await editor.insertText(textNode, text[i], i);
}
await insertUnderscore(editor);
final allBold = textNode.allSatisfyBoldInSelection(Selection.single(
path: [0], startOffset: 0, endOffset: textNode.toPlainText().length));
expect(allBold, false);
expect(textNode.toPlainText(), text);
}));
});
} }

View File

@ -29,7 +29,7 @@ void main() async {
); );
for (final item in defaultSelectionMenuItems) { for (final item in defaultSelectionMenuItems) {
expect(find.text(item.name()), findsOneWidget); expect(find.text(item.name), findsOneWidget);
} }
await editor.updateSelection(Selection.single(path: [1], startOffset: 0)); await editor.updateSelection(Selection.single(path: [1], startOffset: 0));

View File

@ -79,7 +79,7 @@ ShortcutEventHandler _pasteHandler = (editorState, event) {
}; };
SelectionMenuItem codeBlockMenuItem = SelectionMenuItem( SelectionMenuItem codeBlockMenuItem = SelectionMenuItem(
name: () => 'Code Block', name: 'Code Block',
icon: (editorState, onSelected) => Icon( icon: (editorState, onSelected) => Icon(
Icons.abc, Icons.abc,
color: onSelected color: onSelected

View File

@ -34,7 +34,7 @@ ShortcutEventHandler _insertDividerHandler = (editorState, event) {
}; };
SelectionMenuItem dividerMenuItem = SelectionMenuItem( SelectionMenuItem dividerMenuItem = SelectionMenuItem(
name: () => 'Divider', name: 'Divider',
icon: (editorState, onSelected) => Icon( icon: (editorState, onSelected) => Icon(
Icons.horizontal_rule, Icons.horizontal_rule,
color: onSelected color: onSelected

View File

@ -5,7 +5,7 @@ import 'package:flutter/services.dart';
import 'emoji_picker.dart'; import 'emoji_picker.dart';
SelectionMenuItem emojiMenuItem = SelectionMenuItem( SelectionMenuItem emojiMenuItem = SelectionMenuItem(
name: () => 'Emoji', name: 'Emoji',
icon: (editorState, onSelected) => Icon( icon: (editorState, onSelected) => Icon(
Icons.emoji_emotions_outlined, Icons.emoji_emotions_outlined,
color: onSelected color: onSelected

View File

@ -7,7 +7,7 @@ const String kMathEquationType = 'math_equation';
const String kMathEquationAttr = 'math_equation'; const String kMathEquationAttr = 'math_equation';
SelectionMenuItem mathEquationMenuItem = SelectionMenuItem( SelectionMenuItem mathEquationMenuItem = SelectionMenuItem(
name: () => 'Math Equation', name: 'Math Equation',
icon: (editorState, onSelected) => Icon( icon: (editorState, onSelected) => Icon(
Icons.text_fields_rounded, Icons.text_fields_rounded,
color: onSelected color: onSelected