From 249163d51c5e62f44ec46f987ac9bcbef73a6067 Mon Sep 17 00:00:00 2001 From: Yijing Huang Date: Thu, 27 Apr 2023 16:25:19 -0500 Subject: [PATCH 1/7] feat: add bold shortcut --- example/lib/pages/simple_editor.dart | 4 + .../service/shortcut_events.dart | 2 +- .../character_shortcut_events.dart | 4 + .../format_bold.dart | 42 ++++++++ .../format_by_wrapping_with_double_char.dart | 6 ++ ...e_format_by_wrapping_with_double_char.dart | 101 ++++++++++++++++++ 6 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/character_shortcut_events.dart create mode 100644 lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_double_char/format_bold.dart create mode 100644 lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_double_char/format_by_wrapping_with_double_char.dart create mode 100644 lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_double_char/handle_format_by_wrapping_with_double_char.dart diff --git a/example/lib/pages/simple_editor.dart b/example/lib/pages/simple_editor.dart index 996632538..b5e57a0d4 100644 --- a/example/lib/pages/simple_editor.dart +++ b/example/lib/pages/simple_editor.dart @@ -120,6 +120,10 @@ class SimpleEditor extends StatelessWidget { //format strikethrough, ~strikethrough~ formatTildeToStrikethrough, + + //format bold, **bold** or __bold__ + formatDoubleAsterisksToBold, + formatDoubleUnderscoresToBold, ], commandShortcutEvents: [ // backspace diff --git a/lib/src/editor/editor_component/service/shortcut_events.dart b/lib/src/editor/editor_component/service/shortcut_events.dart index 03622e723..15d9bffa9 100644 --- a/lib/src/editor/editor_component/service/shortcut_events.dart +++ b/lib/src/editor/editor_component/service/shortcut_events.dart @@ -1,4 +1,4 @@ -export 'shortcuts/character_shortcut_events.dart'; +export 'shortcuts/character_shortcut_events/character_shortcut_events.dart'; export 'shortcuts/command_shortcut_events.dart'; export 'shortcuts/character_shortcut_event.dart'; diff --git a/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/character_shortcut_events.dart b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/character_shortcut_events.dart new file mode 100644 index 000000000..d9795331f --- /dev/null +++ b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/character_shortcut_events.dart @@ -0,0 +1,4 @@ +export 'insert_newline.dart'; +export 'slash_command.dart'; +export 'format_by_wrapping_with_single_char/format_by_wrapping_with_single_char.dart'; +export 'format_by_wrapping_with_double_char/format_by_wrapping_with_double_char.dart'; diff --git a/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_double_char/format_bold.dart b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_double_char/format_bold.dart new file mode 100644 index 000000000..6e6eaa3b8 --- /dev/null +++ b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_double_char/format_bold.dart @@ -0,0 +1,42 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; + +const _asterisk = '*'; +const _underscore = '_'; + +/// format the text surrounded by double asterisks to bold +/// +/// - support +/// - desktop +/// - mobile +/// - web +/// +CharacterShortcutEvent formatDoubleAsterisksToBold = CharacterShortcutEvent( + key: 'format the text surrounded by double asterisks to bold', + character: _asterisk, + handler: (editorState) async { + return handleFormatByWrappingWithDoubleChar( + editorState: editorState, + char: _asterisk, + formatStyle: DoubleCharacterFormatStyle.bold, + ); + }, +); + +/// format the text surrounded by double underscores to bold +/// +/// - support +/// - desktop +/// - mobile +/// - web +/// +CharacterShortcutEvent formatDoubleUnderscoresToBold = CharacterShortcutEvent( + key: 'format the text surrounded by double underscores to bold', + character: _underscore, + handler: (editorState) async { + return handleFormatByWrappingWithDoubleChar( + editorState: editorState, + char: _underscore, + formatStyle: DoubleCharacterFormatStyle.bold, + ); + }, +); diff --git a/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_double_char/format_by_wrapping_with_double_char.dart b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_double_char/format_by_wrapping_with_double_char.dart new file mode 100644 index 000000000..22e96aa87 --- /dev/null +++ b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_double_char/format_by_wrapping_with_double_char.dart @@ -0,0 +1,6 @@ +// Include all the shortcut(formatting) events triggered by wrapping text with double characters. +// 1. double asterisk to bold -> **abc** +// 2. double underscore to bold -> __abc__ + +export 'format_bold.dart'; +export 'handle_format_by_wrapping_with_double_char.dart'; diff --git a/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_double_char/handle_format_by_wrapping_with_double_char.dart b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_double_char/handle_format_by_wrapping_with_double_char.dart new file mode 100644 index 000000000..247bbb653 --- /dev/null +++ b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_double_char/handle_format_by_wrapping_with_double_char.dart @@ -0,0 +1,101 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; + +// We currently have only one format style is triggered by double characters. +// **abc** or __abc__ -> bold abc +// If we have more in the future, we should add them in this enum and update the [style] variable in [handleDoubleCharactersFormat]. +enum DoubleCharacterFormatStyle { + bold, +} + +bool handleFormatByWrappingWithDoubleChar({ + // for demonstration purpose, the following comments use * to represent the character from the parameter [char]. + required EditorState editorState, + required String char, + required DoubleCharacterFormatStyle formatStyle, +}) { + assert(char.length == 1); + final selection = editorState.selection; + // if the selection is not collapsed, + // we should return false to let the IME handle it. + if (selection == null || !selection.isCollapsed) { + return false; + } + + final path = selection.end.path; + final node = editorState.getNodeAtPath(path); + final delta = node?.delta; + // if the node doesn't contain the delta(which means it isn't a text), + // we don't need to format it. + if (node == null || delta == null) { + return false; + } + + final plainText = delta.toPlainText(); + + // The plainText should look like **abc*, the last char in the plainText should be *[char]. Otherwise, we don't need to format it. + if (plainText.length < 2 || plainText[selection.end.offset - 1] != char) { + return false; + } + + // find all the index of *[char] + final charIndexList = []; + for (var i = 0; i < plainText.length; i++) { + if (plainText[i] == char) { + charIndexList.add(i); + } + } + if (charIndexList.length < 3) { + return false; + } + + // for example: **abc* -> [0, 1, 5] + // thirdLastCharIndex = 0, secondLastCharIndex = 1, lastCharIndex = 5 + // make sure the third *[char] and second *[char] are connected + // make sure the second *[char] and last *[char] are split by at least one character + final thirdLastCharIndex = charIndexList[charIndexList.length - 3]; + final secondLastCharIndex = charIndexList[charIndexList.length - 2]; + final lastCharIndex = charIndexList[charIndexList.length - 1]; + if (secondLastCharIndex != thirdLastCharIndex + 1 || + lastCharIndex == secondLastCharIndex + 1) { + return false; + } + + // if all the conditions are met, we should format the text. + // 1. delete all the *[char] + // 2. update the style of the text surrounded by the double *[char] to [formatStyle] + // 3. update the cursor position. + final deletion = editorState.transaction + ..deleteText(node, lastCharIndex, 1) + ..deleteText(node, thirdLastCharIndex, 2); + editorState.apply(deletion); + + // To minimize errors, retrieve the format style from an enum that is specific to double characters. + final String style; + + switch (formatStyle) { + case DoubleCharacterFormatStyle.bold: + style = 'bold'; + break; + default: + style = ''; + assert(false, 'Invalid format style'); + } + + final format = editorState.transaction + ..formatText( + node, + thirdLastCharIndex, + selection.end.offset - thirdLastCharIndex - 3, + { + style: true, + }, + ) + ..afterSelection = Selection.collapsed( + Position( + path: path, + offset: selection.end.offset - 3, + ), + ); + editorState.apply(format); + return true; +} From 19ede2e276e9ed14729bca371f9e7d2bf0e1997d Mon Sep 17 00:00:00 2001 From: Yijing Huang Date: Thu, 27 Apr 2023 17:15:23 -0500 Subject: [PATCH 2/7] test: add format bold test --- .../format_bold_test.dart | 235 ++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 test/new/service/shortcuts/character_shortcut_events/double_characters_shortcut_events/format_bold_test.dart diff --git a/test/new/service/shortcuts/character_shortcut_events/double_characters_shortcut_events/format_bold_test.dart b/test/new/service/shortcuts/character_shortcut_events/double_characters_shortcut_events/format_bold_test.dart new file mode 100644 index 000000000..5159bd9cb --- /dev/null +++ b/test/new/service/shortcuts/character_shortcut_events/double_characters_shortcut_events/format_bold_test.dart @@ -0,0 +1,235 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; +import '../../../../util/util.dart'; + +void main() async { + group('format the text to bold', () { + setUpAll(() { + if (kDebugMode) { + activateLog(); + } + }); + + tearDownAll(() { + if (kDebugMode) { + deactivateLog(); + } + }); + group('by wrapping with double asterisks', () { + // Before + // **AppFlowy*| + // After + // [bold]AppFlowy + test('**AppFlowy** to bold AppFlowy', () async { + const text = 'AppFlowy'; + final document = Document.blank().addParagraphs( + 1, + builder: (index) => Delta()..insert('**$text*'), + ); + + final editorState = EditorState(document: document); + + // add cursor in the end of the text + final selection = Selection.collapsed( + Position(path: [0], offset: text.length + 3), + ); + editorState.selection = selection; + //run targeted CharacterShortcutEvent = mock adding a * in the end of the text + final result = await formatDoubleAsterisksToBold.execute(editorState); + + expect(result, true); + final after = editorState.getNodeAtPath([0])!; + expect(after.delta!.toPlainText(), text); + expect(after.delta!.toList()[0].attributes, {'bold': true}); + }); + + // Before + // App**Flowy*| + // After + // App[bold]Flowy + test('App**Flowy** to App[bold]Flowy', () async { + const text1 = 'App'; + const text2 = 'Flowy'; + final document = Document.blank().addParagraphs( + 1, + builder: (index) => Delta()..insert('$text1**$text2*'), + ); + + final editorState = EditorState(document: document); + + final selection = Selection.collapsed( + Position(path: [0], offset: text1.length + text2.length + 3), + ); + editorState.selection = selection; + + final result = await formatDoubleAsterisksToBold.execute(editorState); + + expect(result, true); + final after = editorState.getNodeAtPath([0])!; + expect(after.delta!.toPlainText(), '$text1$text2'); + expect(after.delta!.toList()[0].attributes, null); + expect(after.delta!.toList()[1].attributes, {'bold': true}); + }); + + // Before + // ***AppFlowy*| + // After + // *[bold]AppFlowy + test('***AppFlowy** to *[bold]AppFlowy', () async { + const text1 = '*'; + const text2 = 'AppFlowy'; + + final document = Document.blank().addParagraphs( + 1, + builder: (index) => Delta()..insert('**$text1$text2*'), + ); + + final editorState = EditorState(document: document); + + final selection = Selection.collapsed( + Position(path: [0], offset: text1.length + text2.length + 3), + ); + editorState.selection = selection; + + final result = await formatDoubleAsterisksToBold.execute(editorState); + + expect(result, true); + final after = editorState.getNodeAtPath([0])!; + + expect(after.delta!.toPlainText(), text1 + text2); + expect(after.delta!.toList()[0].attributes, null); + expect(after.delta!.toList()[1].attributes, {'bold': true}); + }); + + test('**** nothing changes', () async { + const text = '***`'; + final document = Document.blank().addParagraphs( + 1, + builder: (index) => Delta()..insert(text), + ); + + final editorState = EditorState(document: document); + + final selection = Selection.collapsed( + Position(path: [0], offset: text.length), + ); + editorState.selection = selection; + + final result = await formatDoubleAsterisksToBold.execute(editorState); + + expect(result, false); + final after = editorState.getNodeAtPath([0])!; + expect(after.delta!.toPlainText(), text); + }); + }); + + group('by wrapping with double underscores', () { + // Before + // __AppFlowy_| + // After + // [bold]AppFlowy + test('__AppFlowy__ to bold AppFlowy', () async { + const text = 'AppFlowy'; + final document = Document.blank().addParagraphs( + 1, + builder: (index) => Delta()..insert('__${text}_'), + ); + + final editorState = EditorState(document: document); + + // add cursor in the end of the text + final selection = Selection.collapsed( + Position(path: [0], offset: text.length + 3), + ); + editorState.selection = selection; + //run targeted CharacterShortcutEvent = mock adding a _ in the end of the text + final result = await formatDoubleUnderscoresToBold.execute(editorState); + + expect(result, true); + final after = editorState.getNodeAtPath([0])!; + expect(after.delta!.toPlainText(), text); + expect(after.delta!.toList()[0].attributes, {'bold': true}); + }); + + // Before + // App__Flowy_| + // After + // App[bold]Flowy + test('App__Flowy__ to App[bold]Flowy', () async { + const text1 = 'App'; + const text2 = 'Flowy'; + final document = Document.blank().addParagraphs( + 1, + builder: (index) => Delta()..insert('${text1}__${text2}_'), + ); + + final editorState = EditorState(document: document); + + final selection = Selection.collapsed( + Position(path: [0], offset: text1.length + text2.length + 3), + ); + editorState.selection = selection; + + final result = await formatDoubleUnderscoresToBold.execute(editorState); + + expect(result, true); + final after = editorState.getNodeAtPath([0])!; + expect(after.delta!.toPlainText(), '$text1$text2'); + expect(after.delta!.toList()[0].attributes, null); + expect(after.delta!.toList()[1].attributes, {'bold': true}); + }); + + // Before + // ___AppFlowy_| + // After + // _[bold]AppFlowy + test('___AppFlowy__ to _[bold]AppFlowy', () async { + const text1 = '_'; + const text2 = 'AppFlowy'; + + final document = Document.blank().addParagraphs( + 1, + builder: (index) => Delta()..insert('__$text1${text2}_'), + ); + + final editorState = EditorState(document: document); + + final selection = Selection.collapsed( + Position(path: [0], offset: text1.length + text2.length + 3), + ); + editorState.selection = selection; + + final result = await formatDoubleUnderscoresToBold.execute(editorState); + + expect(result, true); + final after = editorState.getNodeAtPath([0])!; + + expect(after.delta!.toPlainText(), text1 + text2); + expect(after.delta!.toList()[0].attributes, null); + expect(after.delta!.toList()[1].attributes, {'bold': true}); + }); + + test('____ nothing changes', () async { + const text = '___`'; + final document = Document.blank().addParagraphs( + 1, + builder: (index) => Delta()..insert(text), + ); + + final editorState = EditorState(document: document); + + final selection = Selection.collapsed( + Position(path: [0], offset: text.length), + ); + editorState.selection = selection; + + final result = await formatDoubleUnderscoresToBold.execute(editorState); + + expect(result, false); + final after = editorState.getNodeAtPath([0])!; + expect(after.delta!.toPlainText(), text); + }); + }); + }); +} From 191f1e37b7668bb34bab6c656f3ca1fdd67e8594 Mon Sep 17 00:00:00 2001 From: Yijing Huang Date: Fri, 28 Apr 2023 09:31:56 -0500 Subject: [PATCH 3/7] chore: format code --- .../format_strikethrough.dart | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_single_char/format_strikethrough.dart b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_single_char/format_strikethrough.dart index 6ddce2626..c20b95db3 100644 --- a/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_single_char/format_strikethrough.dart +++ b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_single_char/format_strikethrough.dart @@ -10,12 +10,13 @@ const String _tilde = '~'; /// - web /// CharacterShortcutEvent formatTildeToStrikethrough = CharacterShortcutEvent( - key: 'format the text surrounded by single tilde to strikethrough', - character: _tilde, - handler: (editorState) async { - return handleFormatByWrappingWithSingleChar( - editorState: editorState, - char: _tilde, - formatStyle: FormatStyleByWrappingWithSingleChar.strikethrough, - ); - }); + key: 'format the text surrounded by single tilde to strikethrough', + character: _tilde, + handler: (editorState) async { + return handleFormatByWrappingWithSingleChar( + editorState: editorState, + char: _tilde, + formatStyle: FormatStyleByWrappingWithSingleChar.strikethrough, + ); + }, +); From e1348f7553834434dcb6ae9c8c87325007ba5eaa Mon Sep 17 00:00:00 2001 From: Yijing Huang Date: Fri, 28 Apr 2023 17:34:54 -0500 Subject: [PATCH 4/7] fix: fix selection offset error when it is at the head of line --- .../handle_format_by_wrapping_with_double_char.dart | 10 ++++++---- .../handle_format_by_wrapping_with_single_char.dart | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_double_char/handle_format_by_wrapping_with_double_char.dart b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_double_char/handle_format_by_wrapping_with_double_char.dart index 247bbb653..fdf62da92 100644 --- a/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_double_char/handle_format_by_wrapping_with_double_char.dart +++ b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_double_char/handle_format_by_wrapping_with_double_char.dart @@ -15,9 +15,9 @@ bool handleFormatByWrappingWithDoubleChar({ }) { assert(char.length == 1); final selection = editorState.selection; - // if the selection is not collapsed, + // if the selection is not collapsed or the cursor is at the first three index range, we don't need to format it. // we should return false to let the IME handle it. - if (selection == null || !selection.isCollapsed) { + if (selection == null || !selection.isCollapsed || selection.end.offset < 4) { return false; } @@ -32,8 +32,9 @@ bool handleFormatByWrappingWithDoubleChar({ final plainText = delta.toPlainText(); - // The plainText should look like **abc*, the last char in the plainText should be *[char]. Otherwise, we don't need to format it. - if (plainText.length < 2 || plainText[selection.end.offset - 1] != char) { + // The plainText should have at least 4 characters,like **a*. + // The last char in the plainText should be *[char]. Otherwise, we don't need to format it. + if (plainText.length < 4 || plainText[selection.end.offset - 1] != char) { return false; } @@ -44,6 +45,7 @@ bool handleFormatByWrappingWithDoubleChar({ charIndexList.add(i); } } + if (charIndexList.length < 3) { return false; } diff --git a/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_single_char/handle_format_by_wrapping_with_single_char.dart b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_single_char/handle_format_by_wrapping_with_single_char.dart index 5a63ed2d9..c7a214227 100644 --- a/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_single_char/handle_format_by_wrapping_with_single_char.dart +++ b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_single_char/handle_format_by_wrapping_with_single_char.dart @@ -14,9 +14,9 @@ bool handleFormatByWrappingWithSingleChar({ assert(char.length == 1); final selection = editorState.selection; - // if the selection is not collapsed, + // if the selection is not collapsed or the cursor is at the first two index range, we don't need to format it. // we should return false to let the IME handle it. - if (selection == null || !selection.isCollapsed) { + if (selection == null || !selection.isCollapsed || selection.end.offset < 2) { return false; } From 43d806fe18338031f8438d1f52caa747115f0a76 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Sat, 29 Apr 2023 10:58:08 +0800 Subject: [PATCH 5/7] feat: implement format style toolbar item --- assets/images/toolbar/text.svg | 4 +- example/assets/example.json | 2 +- example/lib/pages/simple_editor.dart | 1 + .../toolbar/items/format_toolbar_items.dart | 85 +++++++++++++++---- lib/src/editor/toolbar/toolbar.dart | 1 + 5 files changed, 74 insertions(+), 19 deletions(-) diff --git a/assets/images/toolbar/text.svg b/assets/images/toolbar/text.svg index 7befa5080..70974d5cc 100644 --- a/assets/images/toolbar/text.svg +++ b/assets/images/toolbar/text.svg @@ -1,4 +1,4 @@ - - + + diff --git a/example/assets/example.json b/example/assets/example.json index 2cc085fd2..092948905 100644 --- a/example/assets/example.json +++ b/example/assets/example.json @@ -26,7 +26,7 @@ "attributes": { "delta": [ { "insert": "👋 " }, - { "insert": "Welcome to", "attributes": { "bold": true } }, + { "insert": "Welcome to" }, { "insert": " " }, { "insert": "AppFlowy Editor", diff --git a/example/lib/pages/simple_editor.dart b/example/lib/pages/simple_editor.dart index 996632538..b95d3fac2 100644 --- a/example/lib/pages/simple_editor.dart +++ b/example/lib/pages/simple_editor.dart @@ -43,6 +43,7 @@ class SimpleEditor extends StatelessWidget { heading2Item, heading3Item, placeholderItem, + ...formatItems, ], editorState: editorState, scrollController: scrollController, diff --git a/lib/src/editor/toolbar/items/format_toolbar_items.dart b/lib/src/editor/toolbar/items/format_toolbar_items.dart index cd75ad329..017b7e37e 100644 --- a/lib/src/editor/toolbar/items/format_toolbar_items.dart +++ b/lib/src/editor/toolbar/items/format_toolbar_items.dart @@ -4,22 +4,67 @@ import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/src/editor/toolbar/items/icon_item_widget.dart'; import 'package:flutter/foundation.dart'; -ToolbarItem underlineItem = ToolbarItem( - id: 'editor.paragraph', - isActive: (editorState) => editorState.selection?.isSingle ?? false, - builder: (context, editorState) { - final selection = editorState.selection!; - final node = editorState.getNodeAtPath(selection.start.path)!; - final isHighlight = node.type == 'quote'; - return IconItemWidget( - iconName: 'toolbar/bold', - isHighlight: isHighlight, - tooltip: - '${AppFlowyEditorLocalizations.current.bold}${_shortcutTooltips('⌘ + B', 'CTRL + B', 'CTRL + B')}', - onPressed: () {}, - ); - }, -); +List formatItems = _formatItems + .map( + (e) => ToolbarItem( + id: 'editor.${e.name}', + isActive: (editorState) => editorState.selection?.isSingle ?? false, + builder: (context, editorState) { + final selection = editorState.selection!; + final nodes = editorState.getNodesInSelection(selection); + final isHighlight = nodes.allSatisfyInSelection(selection, (delta) { + return delta.everyAttributes( + (attributes) => attributes[e.name] == true, + ); + }); + return IconItemWidget( + iconName: 'toolbar/${e.name}', + isHighlight: isHighlight, + tooltip: e.tooltip, + onPressed: () {}, + ); + }, + ), + ) + .toList(); + +class _FormatItem { + const _FormatItem({ + required this.name, + required this.tooltip, + }); + + final String name; + final String tooltip; +} + +List<_FormatItem> _formatItems = [ + _FormatItem( + name: 'underline', + tooltip: + '${AppFlowyEditorLocalizations.current.underline}${_shortcutTooltips('⌘ + U', 'CTRL + U', 'CTRL + U')}', + ), + _FormatItem( + name: 'bold', + tooltip: + '${AppFlowyEditorLocalizations.current.bold}${_shortcutTooltips('⌘ + B', 'CTRL + B', 'CTRL + B')}', + ), + _FormatItem( + name: 'italic', + tooltip: + '${AppFlowyEditorLocalizations.current.bold}${_shortcutTooltips('⌘ + I', 'CTRL + I', 'CTRL + I')}', + ), + _FormatItem( + name: 'strikethrough', + tooltip: + '${AppFlowyEditorLocalizations.current.strikethrough}${_shortcutTooltips('⌘ + SHIFT + S', 'CTRL + SHIFT + S', 'CTRL + SHIFT + S')}', + ), + _FormatItem( + name: 'code', + tooltip: + '${AppFlowyEditorLocalizations.current.strikethrough}${_shortcutTooltips('⌘ + E', 'CTRL + E', 'CTRL + E')}', + ), +]; String _shortcutTooltips( String? macOSString, @@ -36,3 +81,11 @@ String _shortcutTooltips( } return ''; } + +extension on Delta { + bool everyAttributes(bool Function(Attributes element) test) => + whereType().every((element) { + final attributes = element.attributes; + return attributes != null && test(attributes); + }); +} diff --git a/lib/src/editor/toolbar/toolbar.dart b/lib/src/editor/toolbar/toolbar.dart index 24e45236b..ffb124478 100644 --- a/lib/src/editor/toolbar/toolbar.dart +++ b/lib/src/editor/toolbar/toolbar.dart @@ -4,3 +4,4 @@ export 'desktop/floating_toolbar_widget.dart'; export 'items/heading_toolbar_items.dart'; export 'items/paragraph_toolbar_item.dart'; export 'items/placeholder_toolbar_item.dart'; +export 'items/format_toolbar_items.dart'; From 33e89ddc8c8a6caeb5b389ef3eb0cb34c8a9d1de Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Sat, 29 Apr 2023 11:22:39 +0800 Subject: [PATCH 6/7] feat: implement the color and link toolbar item --- example/assets/example.json | 4 +- example/lib/pages/simple_editor.dart | 7 + .../items/bulleted_list_toolbar_item.dart | 4 +- .../toolbar/items/color_toolbar_item.dart | 133 ++++++++++++++++++ lib/src/editor/toolbar/items/delta_util.dart | 9 ++ .../toolbar/items/format_toolbar_items.dart | 39 +---- .../toolbar/items/highlight_toolbar_item.dart | 26 ++++ .../toolbar/items/icon_item_widget.dart | 6 +- .../toolbar/items/link_toolbar_item.dart | 25 ++++ .../items/numbered_list_toolbar_item.dart | 4 +- .../toolbar/items/quote_toolbar_item.dart | 4 +- .../editor/toolbar/items/tooltip_util.dart | 19 +++ lib/src/editor/toolbar/toolbar.dart | 8 ++ 13 files changed, 247 insertions(+), 41 deletions(-) create mode 100644 lib/src/editor/toolbar/items/color_toolbar_item.dart create mode 100644 lib/src/editor/toolbar/items/delta_util.dart create mode 100644 lib/src/editor/toolbar/items/highlight_toolbar_item.dart create mode 100644 lib/src/editor/toolbar/items/link_toolbar_item.dart create mode 100644 lib/src/editor/toolbar/items/tooltip_util.dart diff --git a/example/assets/example.json b/example/assets/example.json index 092948905..59363ef31 100644 --- a/example/assets/example.json +++ b/example/assets/example.json @@ -7,7 +7,7 @@ "attributes": { "delta": [ { "insert": "👋 " }, - { "insert": "Welcome to", "attributes": { "bold": true } }, + { "insert": "Welcome to", "attributes": { "italic": true } }, { "insert": " " }, { "insert": "AppFlowy Editor", @@ -26,7 +26,7 @@ "attributes": { "delta": [ { "insert": "👋 " }, - { "insert": "Welcome to" }, + { "insert": "Welcome to", "attributes": { "italic": true } }, { "insert": " " }, { "insert": "AppFlowy Editor", diff --git a/example/lib/pages/simple_editor.dart b/example/lib/pages/simple_editor.dart index b95d3fac2..288c0c260 100644 --- a/example/lib/pages/simple_editor.dart +++ b/example/lib/pages/simple_editor.dart @@ -44,6 +44,13 @@ class SimpleEditor extends StatelessWidget { heading3Item, placeholderItem, ...formatItems, + placeholderItem, + quoteItem, + bulletedListItem, + numberedListItem, + placeholderItem, + linkItem, + colorItem, ], editorState: editorState, scrollController: scrollController, diff --git a/lib/src/editor/toolbar/items/bulleted_list_toolbar_item.dart b/lib/src/editor/toolbar/items/bulleted_list_toolbar_item.dart index 9a20c18d7..e1b84e9c6 100644 --- a/lib/src/editor/toolbar/items/bulleted_list_toolbar_item.dart +++ b/lib/src/editor/toolbar/items/bulleted_list_toolbar_item.dart @@ -1,8 +1,8 @@ import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/src/editor/toolbar/items/icon_item_widget.dart'; -ToolbarItem paragraphItem = ToolbarItem( - id: 'editor.paragraph', +ToolbarItem bulletedListItem = ToolbarItem( + id: 'editor.bulleted_list', isActive: (editorState) => editorState.selection?.isSingle ?? false, builder: (context, editorState) { final selection = editorState.selection!; diff --git a/lib/src/editor/toolbar/items/color_toolbar_item.dart b/lib/src/editor/toolbar/items/color_toolbar_item.dart new file mode 100644 index 000000000..ed3a49708 --- /dev/null +++ b/lib/src/editor/toolbar/items/color_toolbar_item.dart @@ -0,0 +1,133 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor/src/editor/toolbar/items/delta_util.dart'; +import 'package:appflowy_editor/src/editor/toolbar/items/tooltip_util.dart'; +import 'package:appflowy_editor/src/editor/toolbar/items/icon_item_widget.dart'; +import 'package:appflowy_editor/src/render/color_menu/color_picker.dart'; +import 'package:flutter/material.dart'; + +final colorItem = ToolbarItem( + id: 'editor.color', + isActive: (editorState) => editorState.selection?.isSingle ?? false, + builder: (context, editorState) { + final selection = editorState.selection!; + final nodes = editorState.getNodesInSelection(selection); + final isHighlight = nodes.allSatisfyInSelection(selection, (delta) { + return delta.everyAttributes( + (attributes) { + // TODO: refactor this part. + // just copy from the origin code. + final color = attributes['color']; + final backgroundColor = attributes['backgroundColor']; + final defaultColor = _generateFontColorOptions( + editorState, + ).first.colorHex; + final defaultBackgroundColor = _generateBackgroundColorOptions( + editorState, + ).first.colorHex; + return (color != null && color != defaultColor) || + (backgroundColor != null && + backgroundColor != defaultBackgroundColor); + }, + ); + }); + return IconItemWidget( + iconName: 'toolbar/highlight', + isHighlight: isHighlight, + tooltip: + '${AppFlowyEditorLocalizations.current.link}${shortcutTooltips("⌘ + K", "CTRL + K", "CTRL + K")}', + onPressed: () {}, + ); + }, +); + +List _generateFontColorOptions(EditorState editorState) { + final defaultColor = + editorState.editorStyle.textStyle?.color ?? Colors.black; // black + return [ + ColorOption( + colorHex: defaultColor.toHex(), + name: AppFlowyEditorLocalizations.current.fontColorDefault, + ), + ColorOption( + colorHex: Colors.grey.toHex(), + name: AppFlowyEditorLocalizations.current.fontColorGray, + ), + ColorOption( + colorHex: Colors.brown.toHex(), + name: AppFlowyEditorLocalizations.current.fontColorBrown, + ), + ColorOption( + colorHex: Colors.yellow.toHex(), + name: AppFlowyEditorLocalizations.current.fontColorYellow, + ), + ColorOption( + colorHex: Colors.green.toHex(), + name: AppFlowyEditorLocalizations.current.fontColorGreen, + ), + ColorOption( + colorHex: Colors.blue.toHex(), + name: AppFlowyEditorLocalizations.current.fontColorBlue, + ), + ColorOption( + colorHex: Colors.purple.toHex(), + name: AppFlowyEditorLocalizations.current.fontColorPurple, + ), + ColorOption( + colorHex: Colors.pink.toHex(), + name: AppFlowyEditorLocalizations.current.fontColorPink, + ), + ColorOption( + colorHex: Colors.red.toHex(), + name: AppFlowyEditorLocalizations.current.fontColorRed, + ), + ]; +} + +List _generateBackgroundColorOptions(EditorState editorState) { + final defaultBackgroundColorHex = + editorState.editorStyle.highlightColorHex ?? '0x6000BCF0'; + return [ + ColorOption( + colorHex: defaultBackgroundColorHex, + name: AppFlowyEditorLocalizations.current.backgroundColorDefault, + ), + ColorOption( + colorHex: Colors.grey.withOpacity(0.3).toHex(), + name: AppFlowyEditorLocalizations.current.backgroundColorGray, + ), + ColorOption( + colorHex: Colors.brown.withOpacity(0.3).toHex(), + name: AppFlowyEditorLocalizations.current.backgroundColorBrown, + ), + ColorOption( + colorHex: Colors.yellow.withOpacity(0.3).toHex(), + name: AppFlowyEditorLocalizations.current.backgroundColorYellow, + ), + ColorOption( + colorHex: Colors.green.withOpacity(0.3).toHex(), + name: AppFlowyEditorLocalizations.current.backgroundColorGreen, + ), + ColorOption( + colorHex: Colors.blue.withOpacity(0.3).toHex(), + name: AppFlowyEditorLocalizations.current.backgroundColorBlue, + ), + ColorOption( + colorHex: Colors.purple.withOpacity(0.3).toHex(), + name: AppFlowyEditorLocalizations.current.backgroundColorPurple, + ), + ColorOption( + colorHex: Colors.pink.withOpacity(0.3).toHex(), + name: AppFlowyEditorLocalizations.current.backgroundColorPink, + ), + ColorOption( + colorHex: Colors.red.withOpacity(0.3).toHex(), + name: AppFlowyEditorLocalizations.current.backgroundColorRed, + ), + ]; +} + +extension on Color { + String toHex() { + return '0x${value.toRadixString(16)}'; + } +} diff --git a/lib/src/editor/toolbar/items/delta_util.dart b/lib/src/editor/toolbar/items/delta_util.dart new file mode 100644 index 000000000..e39238386 --- /dev/null +++ b/lib/src/editor/toolbar/items/delta_util.dart @@ -0,0 +1,9 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; + +extension AttributesDelta on Delta { + bool everyAttributes(bool Function(Attributes element) test) => + whereType().every((element) { + final attributes = element.attributes; + return attributes != null && test(attributes); + }); +} diff --git a/lib/src/editor/toolbar/items/format_toolbar_items.dart b/lib/src/editor/toolbar/items/format_toolbar_items.dart index 017b7e37e..1987f7318 100644 --- a/lib/src/editor/toolbar/items/format_toolbar_items.dart +++ b/lib/src/editor/toolbar/items/format_toolbar_items.dart @@ -1,8 +1,7 @@ -import 'dart:io'; - import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor/src/editor/toolbar/items/delta_util.dart'; +import 'package:appflowy_editor/src/editor/toolbar/items/tooltip_util.dart'; import 'package:appflowy_editor/src/editor/toolbar/items/icon_item_widget.dart'; -import 'package:flutter/foundation.dart'; List formatItems = _formatItems .map( @@ -42,50 +41,26 @@ List<_FormatItem> _formatItems = [ _FormatItem( name: 'underline', tooltip: - '${AppFlowyEditorLocalizations.current.underline}${_shortcutTooltips('⌘ + U', 'CTRL + U', 'CTRL + U')}', + '${AppFlowyEditorLocalizations.current.underline}${shortcutTooltips('⌘ + U', 'CTRL + U', 'CTRL + U')}', ), _FormatItem( name: 'bold', tooltip: - '${AppFlowyEditorLocalizations.current.bold}${_shortcutTooltips('⌘ + B', 'CTRL + B', 'CTRL + B')}', + '${AppFlowyEditorLocalizations.current.bold}${shortcutTooltips('⌘ + B', 'CTRL + B', 'CTRL + B')}', ), _FormatItem( name: 'italic', tooltip: - '${AppFlowyEditorLocalizations.current.bold}${_shortcutTooltips('⌘ + I', 'CTRL + I', 'CTRL + I')}', + '${AppFlowyEditorLocalizations.current.bold}${shortcutTooltips('⌘ + I', 'CTRL + I', 'CTRL + I')}', ), _FormatItem( name: 'strikethrough', tooltip: - '${AppFlowyEditorLocalizations.current.strikethrough}${_shortcutTooltips('⌘ + SHIFT + S', 'CTRL + SHIFT + S', 'CTRL + SHIFT + S')}', + '${AppFlowyEditorLocalizations.current.strikethrough}${shortcutTooltips('⌘ + SHIFT + S', 'CTRL + SHIFT + S', 'CTRL + SHIFT + S')}', ), _FormatItem( name: 'code', tooltip: - '${AppFlowyEditorLocalizations.current.strikethrough}${_shortcutTooltips('⌘ + E', 'CTRL + E', 'CTRL + E')}', + '${AppFlowyEditorLocalizations.current.strikethrough}${shortcutTooltips('⌘ + E', 'CTRL + E', 'CTRL + E')}', ), ]; - -String _shortcutTooltips( - String? macOSString, - String? windowsString, - String? linuxString, -) { - if (kIsWeb) return ''; - if (Platform.isMacOS && macOSString != null) { - return '\n$macOSString'; - } else if (Platform.isWindows && windowsString != null) { - return '\n$windowsString'; - } else if (Platform.isLinux && linuxString != null) { - return '\n$linuxString'; - } - return ''; -} - -extension on Delta { - bool everyAttributes(bool Function(Attributes element) test) => - whereType().every((element) { - final attributes = element.attributes; - return attributes != null && test(attributes); - }); -} diff --git a/lib/src/editor/toolbar/items/highlight_toolbar_item.dart b/lib/src/editor/toolbar/items/highlight_toolbar_item.dart new file mode 100644 index 000000000..f99384728 --- /dev/null +++ b/lib/src/editor/toolbar/items/highlight_toolbar_item.dart @@ -0,0 +1,26 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor/src/editor/toolbar/items/delta_util.dart'; +import 'package:appflowy_editor/src/editor/toolbar/items/tooltip_util.dart'; +import 'package:appflowy_editor/src/editor/toolbar/items/icon_item_widget.dart'; + +// unused now. +final highlightItem = ToolbarItem( + id: 'editor.highlight', + isActive: (editorState) => editorState.selection?.isSingle ?? false, + builder: (context, editorState) { + final selection = editorState.selection!; + final nodes = editorState.getNodesInSelection(selection); + final isHighlight = nodes.allSatisfyInSelection(selection, (delta) { + return delta.everyAttributes( + (attributes) => attributes['href'] != null, + ); + }); + return IconItemWidget( + iconName: 'toolbar/link', + isHighlight: isHighlight, + tooltip: + '${AppFlowyEditorLocalizations.current.link}${shortcutTooltips("⌘ + K", "CTRL + K", "CTRL + K")}', + onPressed: () {}, + ); + }, +); diff --git a/lib/src/editor/toolbar/items/icon_item_widget.dart b/lib/src/editor/toolbar/items/icon_item_widget.dart index 7cc4154c6..8c639b76d 100644 --- a/lib/src/editor/toolbar/items/icon_item_widget.dart +++ b/lib/src/editor/toolbar/items/icon_item_widget.dart @@ -4,7 +4,8 @@ import 'package:flutter/material.dart'; class IconItemWidget extends StatelessWidget { const IconItemWidget({ super.key, - this.size = const Size.square(32.0), + this.size = const Size.square(30.0), + this.iconSize = const Size.square(18.0), required this.iconName, required this.isHighlight, this.tooltip, @@ -12,6 +13,7 @@ class IconItemWidget extends StatelessWidget { }); final Size size; + final Size iconSize; final String iconName; final bool isHighlight; final String? tooltip; @@ -22,6 +24,8 @@ class IconItemWidget extends StatelessWidget { Widget child = FlowySvg( name: iconName, color: isHighlight ? Colors.lightBlue : null, + width: iconSize.width, + height: iconSize.height, ); if (onPressed != null) { child = MouseRegion( diff --git a/lib/src/editor/toolbar/items/link_toolbar_item.dart b/lib/src/editor/toolbar/items/link_toolbar_item.dart new file mode 100644 index 000000000..94902da63 --- /dev/null +++ b/lib/src/editor/toolbar/items/link_toolbar_item.dart @@ -0,0 +1,25 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor/src/editor/toolbar/items/delta_util.dart'; +import 'package:appflowy_editor/src/editor/toolbar/items/tooltip_util.dart'; +import 'package:appflowy_editor/src/editor/toolbar/items/icon_item_widget.dart'; + +final linkItem = ToolbarItem( + id: 'editor.link', + isActive: (editorState) => editorState.selection?.isSingle ?? false, + builder: (context, editorState) { + final selection = editorState.selection!; + final nodes = editorState.getNodesInSelection(selection); + final isHighlight = nodes.allSatisfyInSelection(selection, (delta) { + return delta.everyAttributes( + (attributes) => attributes['href'] != null, + ); + }); + return IconItemWidget( + iconName: 'toolbar/link', + isHighlight: isHighlight, + tooltip: + '${AppFlowyEditorLocalizations.current.link}${shortcutTooltips("⌘ + K", "CTRL + K", "CTRL + K")}', + onPressed: () {}, + ); + }, +); diff --git a/lib/src/editor/toolbar/items/numbered_list_toolbar_item.dart b/lib/src/editor/toolbar/items/numbered_list_toolbar_item.dart index 3cb39d657..9a9599c54 100644 --- a/lib/src/editor/toolbar/items/numbered_list_toolbar_item.dart +++ b/lib/src/editor/toolbar/items/numbered_list_toolbar_item.dart @@ -1,8 +1,8 @@ import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/src/editor/toolbar/items/icon_item_widget.dart'; -ToolbarItem paragraphItem = ToolbarItem( - id: 'editor.paragraph', +ToolbarItem numberedListItem = ToolbarItem( + id: 'editor.numbered_list', isActive: (editorState) => editorState.selection?.isSingle ?? false, builder: (context, editorState) { final selection = editorState.selection!; diff --git a/lib/src/editor/toolbar/items/quote_toolbar_item.dart b/lib/src/editor/toolbar/items/quote_toolbar_item.dart index 981a15c7f..d727139ce 100644 --- a/lib/src/editor/toolbar/items/quote_toolbar_item.dart +++ b/lib/src/editor/toolbar/items/quote_toolbar_item.dart @@ -1,8 +1,8 @@ import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/src/editor/toolbar/items/icon_item_widget.dart'; -ToolbarItem paragraphItem = ToolbarItem( - id: 'editor.paragraph', +ToolbarItem quoteItem = ToolbarItem( + id: 'editor.quote', isActive: (editorState) => editorState.selection?.isSingle ?? false, builder: (context, editorState) { final selection = editorState.selection!; diff --git a/lib/src/editor/toolbar/items/tooltip_util.dart b/lib/src/editor/toolbar/items/tooltip_util.dart new file mode 100644 index 000000000..847a00f3c --- /dev/null +++ b/lib/src/editor/toolbar/items/tooltip_util.dart @@ -0,0 +1,19 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; + +String shortcutTooltips( + String? macOSString, + String? windowsString, + String? linuxString, +) { + if (kIsWeb) return ''; + if (Platform.isMacOS && macOSString != null) { + return '\n$macOSString'; + } else if (Platform.isWindows && windowsString != null) { + return '\n$windowsString'; + } else if (Platform.isLinux && linuxString != null) { + return '\n$linuxString'; + } + return ''; +} diff --git a/lib/src/editor/toolbar/toolbar.dart b/lib/src/editor/toolbar/toolbar.dart index ffb124478..e7d27aea7 100644 --- a/lib/src/editor/toolbar/toolbar.dart +++ b/lib/src/editor/toolbar/toolbar.dart @@ -5,3 +5,11 @@ export 'items/heading_toolbar_items.dart'; export 'items/paragraph_toolbar_item.dart'; export 'items/placeholder_toolbar_item.dart'; export 'items/format_toolbar_items.dart'; + +export 'items/bulleted_list_toolbar_item.dart'; +export 'items/numbered_list_toolbar_item.dart'; +export 'items/quote_toolbar_item.dart'; + +export 'items/link_toolbar_item.dart'; +export 'items/highlight_toolbar_item.dart'; +export 'items/color_toolbar_item.dart'; From 137de32a5aa70358b18bdfdf0d307caeafbf4c4c Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Sat, 29 Apr 2023 11:32:07 +0800 Subject: [PATCH 7/7] chore: format the code --- .../format_code.dart | 15 +++++++-------- .../format_italic.dart | 19 +++++++++---------- .../format_strikethrough.dart | 19 ++++++++++--------- ...e_format_by_wrapping_with_single_char.dart | 12 ++++++------ .../insert_newline.dart | 2 +- .../slash_command.dart | 2 +- 6 files changed, 34 insertions(+), 35 deletions(-) diff --git a/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_single_char/format_code.dart b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_single_char/format_code.dart index 873c5f2e8..ffc238067 100644 --- a/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_single_char/format_code.dart +++ b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_single_char/format_code.dart @@ -9,14 +9,13 @@ const _backquote = '`'; /// - mobile /// - web /// -CharacterShortcutEvent formatBackquoteToCode = CharacterShortcutEvent( +final CharacterShortcutEvent formatBackquoteToCode = CharacterShortcutEvent( key: 'format the text surrounded by single backquote to code', character: _backquote, - handler: (editorState) async { - return handleFormatByWrappingWithSingleChar( - editorState: editorState, - char: _backquote, - formatStyle: FormatStyleByWrappingWithSingleChar.code, - ); - }, + handler: (editorState) async => + await handleFormatByWrappingWithSingleCharacter( + editorState: editorState, + character: _backquote, + formatStyle: FormatStyleByWrappingWithSingleChar.code, + ), ); diff --git a/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_single_char/format_italic.dart b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_single_char/format_italic.dart index 7e5f4a4a6..818edeb48 100644 --- a/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_single_char/format_italic.dart +++ b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_single_char/format_italic.dart @@ -14,9 +14,9 @@ CharacterShortcutEvent formatUnderscoreToItalic = CharacterShortcutEvent( key: 'format the text surrounded by single underscore to italic', character: _underscore, handler: (editorState) async { - return handleFormatByWrappingWithSingleChar( + return handleFormatByWrappingWithSingleCharacter( editorState: editorState, - char: _underscore, + character: _underscore, formatStyle: FormatStyleByWrappingWithSingleChar.italic, ); }, @@ -29,14 +29,13 @@ CharacterShortcutEvent formatUnderscoreToItalic = CharacterShortcutEvent( /// - mobile /// - web /// -CharacterShortcutEvent formatAsteriskToItalic = CharacterShortcutEvent( +final CharacterShortcutEvent formatAsteriskToItalic = CharacterShortcutEvent( key: 'format the text surrounded by single asterisk to italic', character: _asterisk, - handler: (editorState) async { - return handleFormatByWrappingWithSingleChar( - editorState: editorState, - char: _asterisk, - formatStyle: FormatStyleByWrappingWithSingleChar.italic, - ); - }, + handler: (editorState) async => + await handleFormatByWrappingWithSingleCharacter( + editorState: editorState, + character: _asterisk, + formatStyle: FormatStyleByWrappingWithSingleChar.italic, + ), ); diff --git a/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_single_char/format_strikethrough.dart b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_single_char/format_strikethrough.dart index 6ddce2626..723e41b10 100644 --- a/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_single_char/format_strikethrough.dart +++ b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_single_char/format_strikethrough.dart @@ -9,13 +9,14 @@ const String _tilde = '~'; /// - mobile /// - web /// -CharacterShortcutEvent formatTildeToStrikethrough = CharacterShortcutEvent( - key: 'format the text surrounded by single tilde to strikethrough', +final CharacterShortcutEvent formatTildeToStrikethrough = + CharacterShortcutEvent( + key: 'format the text surrounded by single tilde to strikethrough', + character: _tilde, + handler: (editorState) async => + await handleFormatByWrappingWithSingleCharacter( + editorState: editorState, character: _tilde, - handler: (editorState) async { - return handleFormatByWrappingWithSingleChar( - editorState: editorState, - char: _tilde, - formatStyle: FormatStyleByWrappingWithSingleChar.strikethrough, - ); - }); + formatStyle: FormatStyleByWrappingWithSingleChar.strikethrough, + ), +); diff --git a/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_single_char/handle_format_by_wrapping_with_single_char.dart b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_single_char/handle_format_by_wrapping_with_single_char.dart index 5a63ed2d9..c9296bbec 100644 --- a/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_single_char/handle_format_by_wrapping_with_single_char.dart +++ b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_by_wrapping_with_single_char/handle_format_by_wrapping_with_single_char.dart @@ -6,12 +6,12 @@ enum FormatStyleByWrappingWithSingleChar { strikethrough, } -bool handleFormatByWrappingWithSingleChar({ +Future handleFormatByWrappingWithSingleCharacter({ required EditorState editorState, - required String char, + required String character, required FormatStyleByWrappingWithSingleChar formatStyle, -}) { - assert(char.length == 1); +}) async { + assert(character.length == 1); final selection = editorState.selection; // if the selection is not collapsed, @@ -31,8 +31,8 @@ bool handleFormatByWrappingWithSingleChar({ final plainText = delta.toPlainText(); - final headCharIndex = plainText.indexOf(char); - final endCharIndex = plainText.lastIndexOf(char); + final headCharIndex = plainText.indexOf(character); + final endCharIndex = plainText.lastIndexOf(character); // Determine if a 'Character' already exists in the node and only once. // 1. This is no 'Character' in the plainText: indexOf returns -1. diff --git a/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/insert_newline.dart b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/insert_newline.dart index 173fa0fac..37958dce7 100644 --- a/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/insert_newline.dart +++ b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/insert_newline.dart @@ -10,7 +10,7 @@ import 'package:flutter/services.dart'; /// - mobile /// - web /// -CharacterShortcutEvent insertNewLine = CharacterShortcutEvent( +final CharacterShortcutEvent insertNewLine = CharacterShortcutEvent( key: 'insert a new line', character: '\n', handler: _insertNewLineHandler, diff --git a/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/slash_command.dart b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/slash_command.dart index 1267af16c..9a24803b2 100644 --- a/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/slash_command.dart +++ b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/slash_command.dart @@ -6,7 +6,7 @@ import 'package:appflowy_editor/appflowy_editor.dart'; /// - desktop /// - web /// -CharacterShortcutEvent slashCommand = CharacterShortcutEvent( +final CharacterShortcutEvent slashCommand = CharacterShortcutEvent( key: 'show the slash menu', character: '/', handler: _showSlashMenu,