Skip to content

Commit

Permalink
Merge branch 'mobile' into feat/to_do_list_charcter_shortcut
Browse files Browse the repository at this point in the history
  • Loading branch information
LucasXu0 authored Apr 29, 2023
2 parents 378cbfb + 9f8a305 commit 10ccd61
Show file tree
Hide file tree
Showing 26 changed files with 737 additions and 82 deletions.
4 changes: 2 additions & 2 deletions assets/images/toolbar/text.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions example/assets/example.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"attributes": {
"delta": [
{ "insert": "👋 " },
{ "insert": "Welcome to", "attributes": { "bold": true } },
{ "insert": "Welcome to", "attributes": { "italic": true } },
{ "insert": " " },
{
"insert": "AppFlowy Editor",
Expand All @@ -26,7 +26,7 @@
"attributes": {
"delta": [
{ "insert": "👋 " },
{ "insert": "Welcome to", "attributes": { "bold": true } },
{ "insert": "Welcome to", "attributes": { "italic": true } },
{ "insert": " " },
{
"insert": "AppFlowy Editor",
Expand Down
12 changes: 12 additions & 0 deletions example/lib/pages/simple_editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ class SimpleEditor extends StatelessWidget {
heading2Item,
heading3Item,
placeholderItem,
...formatItems,
placeholderItem,
quoteItem,
bulletedListItem,
numberedListItem,
placeholderItem,
linkItem,
colorItem,
],
editorState: editorState,
scrollController: scrollController,
Expand Down Expand Up @@ -128,6 +136,10 @@ class SimpleEditor extends StatelessWidget {
// format checked box, [x] or -[x]
formatFilledBracketsToCheckedBox,
formatHyphenFilledBracketsToCheckedBox,

//format bold, **bold** or __bold__
formatDoubleAsterisksToBold,
formatDoubleUnderscoresToBold,
],
commandShortcutEvents: [
// backspace
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
@@ -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,
);
},
);
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
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 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 || selection.end.offset < 4) {
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 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;
}

// find all the index of *[char]
final charIndexList = <int>[];
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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
),
);
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
},
Expand All @@ -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,
),
);
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ const String _tilde = '~';
/// - mobile
/// - 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,
);
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ enum FormatStyleByWrappingWithSingleChar {
strikethrough,
}

bool handleFormatByWrappingWithSingleChar({
Future<bool> 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,
// 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;
}

Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions lib/src/editor/toolbar/items/bulleted_list_toolbar_item.dart
Original file line number Diff line number Diff line change
@@ -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!;
Expand Down
Loading

0 comments on commit 10ccd61

Please sign in to comment.