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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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';
SelectionMenuItem boardMenuItem = SelectionMenuItem(
name: () => LocaleKeys.document_plugins_referencedBoard.tr(),
name: LocaleKeys.document_plugins_referencedBoard.tr(),
icon: (editorState, onSelected) {
return svgWidget(
'editor/board',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -265,135 +265,6 @@ ShortcutEventHandler markdownLinkOrImageHandler = (editorState, event) {
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) {
// 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.
@ -438,3 +309,117 @@ ShortcutEventHandler underscoreToItalicHandler = (editorState, event) {
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/backspace_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',
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(
key: 'Backquote to code',
command: 'backquote',
@ -319,6 +307,16 @@ List<ShortcutEvent> builtInShortcutEvents = [
command: 'shift+underscore',
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
// 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

View File

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

View File

@ -70,7 +70,7 @@ void main() async {
});
// 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',
(tester) async {
// Before

View File

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

View File

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

View File

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

View File

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

View File

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