fix: convert markdown symbol to styled text fails (#1090)

* fix: convert markdown symbol to styled text fails

* docs: update customzing.md

* chore: disable macOS titlebar

* chore: specify the built-in language as en_US

* docs: update documentation

* docs: update documentation
This commit is contained in:
Lucas.Xu 2022-09-19 18:10:48 +08:00 committed by GitHub
parent 5896855dda
commit 3e75b1ac92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 139 additions and 86 deletions

View File

@ -27,7 +27,7 @@ and the Flutter guide for
</p>
<div align="center">
<img src="https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/documentation/images/appflowy-editor-example.gif?raw=true" width = "700" style = "padding: 100"/>
<img src="https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/documentation/images/appflowy_editor_example.mp4?raw=true" width = "700" style = "padding: 100"/>
</div>
## Key Features
@ -58,8 +58,6 @@ final editorStyle = EditorStyle.defaultStyle();
final editorState = EditorState.empty(); // an empty state
final editor = AppFlowyEditor(
editorState: editorState,
shortcutEvents: const [],
customBuilders: const {},
editorStyle: editorStyle,
);
```
@ -72,8 +70,6 @@ final editorStyle = EditorStyle.defaultStyle();
final editorState = EditorState(StateTree.fromJson(data));
final editor = AppFlowyEditor(
editorState: editorState,
shortcutEvents: const [],
customBuilders: const {},
editorStyle: editorStyle,
);
```
@ -113,7 +109,7 @@ Please refer to our documentation on customizing AppFlowy for a detailed discuss
Below are some examples of shortcut event customizations:
* [BIUS](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/update_text_style_by_command_x_handler.dart) demonstrates how to make text bold/italic/underline/strikethrough through shortcut keys
* [BIUS](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/format_style_handler.dart) demonstrates how to make text bold/italic/underline/strikethrough through shortcut keys
* [Paste HTML](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart) gives you an idea on how to handle pasted styles through shortcut keys
* Need more examples? Check out [Internal key event handlers](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers)

View File

@ -27,7 +27,7 @@ Widget build(BuildContext context) {
At this point, nothing magic will happen after typing `_xxx_`.
![Before](./images/customizing_a_shortcut_event_before.gif)
![Before](./images/customize_a_shortcut_event_before.gif)
To implement our shortcut event we will create a `ShortcutEvent` instance to handle an underscore input.
@ -39,11 +39,10 @@ We need to define `key` and `command` in a ShortCutEvent object to customize hot
```dart
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
ShortcutEvent underscoreToItalicEvent = ShortcutEvent(
key: 'Underscore to italic',
command: 'underscore',
command: 'shift+underscore',
handler: _underscoreToItalicHandler,
);
@ -82,9 +81,12 @@ ShortcutEventHandler _underscoreToItalicHandler = (editorState, event) {
final textNode = textNodes.first;
final text = textNode.toRawString();
// Determine if an 'underscore' already exists in the text node
final previousUnderscore = text.indexOf('_');
if (previousUnderscore == -1) {
// Determine if an 'underscore' already exists in the text node and only once.
final firstUnderscore = text.indexOf('_');
final lastUnderscore = text.lastIndexOf('_');
if (firstUnderscore == -1 ||
firstUnderscore != lastUnderscore ||
firstUnderscore == selection.start.offset - 1) {
return KeyEventResult.ignored;
}
@ -92,15 +94,20 @@ ShortcutEventHandler _underscoreToItalicHandler = (editorState, event) {
// update the style of the text surrounded by the two underscores to 'italic',
// and update the cursor position.
TransactionBuilder(editorState)
..deleteText(textNode, previousUnderscore, 1)
..deleteText(textNode, firstUnderscore, 1)
..formatText(
textNode,
previousUnderscore,
selection.end.offset - previousUnderscore - 1,
{'italic': true},
firstUnderscore,
selection.end.offset - firstUnderscore - 1,
{
BuiltInAttributeKey.italic: true,
},
)
..afterSelection = Selection.collapsed(
Position(path: textNode.path, offset: selection.end.offset - 1),
Position(
path: textNode.path,
offset: selection.end.offset - 1,
),
)
..commit();
@ -121,7 +128,7 @@ Widget build(BuildContext context) {
editorStyle: EditorStyle.defaultStyle(),
customBuilders: const {},
shortcutEvents: [
_underscoreToItalicHandler,
underscoreToItalic,
],
),
),
@ -129,9 +136,9 @@ Widget build(BuildContext context) {
}
```
![After](./images/customizing_a_shortcut_event_after.gif)
![After](./images/customize_a_shortcut_event_after.gif)
Check out the [complete code](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/underscore_to_italic_key_event_handler.dart) file of this example.
Check out the [complete code](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/underscore_to_italic.dart) file of this example.
## Customizing a Component
@ -297,7 +304,7 @@ return AppFlowyEditor(
);
```
![Whew!](./images/customizing_a_component.gif)
![Whew!](./images/customize_a_component.gif)
Check out the [complete code](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/network_image_node_widget.dart) file of this example.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 998 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 MiB

View File

@ -1,7 +1,7 @@
import 'dart:convert';
import 'dart:io';
import 'package:example/plugin/underscore_to_italic_key_event_handler.dart';
import 'package:example/plugin/underscore_to_italic.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
@ -29,7 +29,7 @@ class MyApp extends StatelessWidget {
GlobalWidgetsLocalizations.delegate,
AppFlowyEditorLocalizations.delegate,
],
supportedLocales: AppFlowyEditorLocalizations.delegate.supportedLocales,
supportedLocales: const [Locale('en', 'US')],
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
@ -113,7 +113,7 @@ class _MyHomePageState extends State<MyHomePage> {
editorState: _editorState!,
editorStyle: _editorStyle,
shortcutEvents: [
underscoreToItalicEvent,
underscoreToItalic,
],
),
);
@ -186,6 +186,7 @@ class _MyHomePageState extends State<MyHomePage> {
final path = directory.path;
final file = File('$path/editor.json');
setState(() {
_editorState = null;
_jsonString = file.readAsString();
});
}

View File

@ -0,0 +1,53 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
ShortcutEvent underscoreToItalic = ShortcutEvent(
key: 'Underscore to italic',
command: 'shift+underscore',
handler: _underscoreToItalicHandler,
);
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.
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.toRawString();
// Determine if an 'underscore' already exists in the text node and only once.
final firstUnderscore = text.indexOf('_');
final lastUnderscore = text.lastIndexOf('_');
if (firstUnderscore == -1 ||
firstUnderscore != lastUnderscore ||
firstUnderscore == selection.start.offset - 1) {
return KeyEventResult.ignored;
}
// Delete the previous 'underscore',
// update the style of the text surrounded by the two underscores to 'italic',
// and update the cursor position.
TransactionBuilder(editorState)
..deleteText(textNode, firstUnderscore, 1)
..formatText(
textNode,
firstUnderscore,
selection.end.offset - firstUnderscore - 1,
{
BuiltInAttributeKey.italic: true,
},
)
..afterSelection = Selection.collapsed(
Position(
path: textNode.path,
offset: selection.end.offset - 1,
),
)
..commit();
return KeyEventResult.handled;
};

View File

@ -1,45 +0,0 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
ShortcutEvent underscoreToItalicEvent = ShortcutEvent(
key: 'Underscore to italic',
command: 'underscore',
handler: _underscoreToItalicHandler,
);
ShortcutEventHandler _underscoreToItalicHandler = (editorState, event) {
// Obtaining the selection and selected nodes of the current document through `selectionService`,
// and determine whether it is a single selection and whether the selected node is a text node.
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.toRawString();
// Determine if `underscore` already exists in the text node
final previousUnderscore = text.indexOf('_');
if (previousUnderscore == -1) {
return KeyEventResult.ignored;
}
// Delete the previous `underscore`,
// update the style of the text surrounded by two underscores to `italic`,
// and update the cursor position.
TransactionBuilder(editorState)
..deleteText(textNode, previousUnderscore, 1)
..formatText(
textNode,
previousUnderscore,
selection.end.offset - previousUnderscore - 1,
{'italic': true},
)
..afterSelection = Selection.collapsed(
Position(path: textNode.path, offset: selection.end.offset - 1),
)
..commit();
return KeyEventResult.handled;
};

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21225" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21225"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -13,7 +13,7 @@
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target">
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="example" customModuleProvider="target">
<connections>
<outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/>
<outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
@ -330,14 +330,15 @@
</items>
<point key="canvasLocation" x="142" y="-258"/>
</menu>
<window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" titlebarAppearsTransparent="YES" titleVisibility="hidden" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="example" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES" fullSizeContentView="YES"/>
<rect key="contentRect" x="335" y="390" width="800" height="600"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1055"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
<autoresizingMask key="autoresizingMask"/>
</view>
<point key="canvasLocation" x="126" y="-658"/>
</window>
</objects>
</document>

View File

@ -10,6 +10,7 @@ export 'src/document/selection.dart';
export 'src/document/state_tree.dart';
export 'src/document/text_delta.dart';
export 'src/document/attributes.dart';
export 'src/document/built_in_attribute_keys.dart';
export 'src/editor_state.dart';
export 'src/operation/operation.dart';
export 'src/operation/transaction.dart';

View File

@ -44,10 +44,17 @@ ShortcutEventHandler whiteSpaceHandler = (editorState, event) {
}
final textNode = textNodes.first;
final text = textNode.toRawString();
final text = textNode.toRawString().substring(0, selection.end.offset);
final numberMatch = _numberRegex.firstMatch(text);
if (numberMatch != null) {
if ((_checkboxListSymbols + _unCheckboxListSymbols).contains(text)) {
return _toCheckboxList(editorState, textNode);
} else if (_bulletedListSymbols.contains(text)) {
return _toBulletedList(editorState, textNode);
} else if (_countOfSign(text, selection) != 0) {
return _toHeadingStyle(editorState, textNode, selection);
} else if (numberMatch != null) {
final matchText = numberMatch.group(0);
final numText = numberMatch.group(1);
if (matchText != null && numText != null) {
@ -55,14 +62,6 @@ ShortcutEventHandler whiteSpaceHandler = (editorState, event) {
}
}
if ((_checkboxListSymbols + _unCheckboxListSymbols).any(text.startsWith)) {
return _toCheckboxList(editorState, textNode);
} else if (_bulletedListSymbols.any(text.startsWith)) {
return _toBulletedList(editorState, textNode);
} else if (_countOfSign(text, selection) != 0) {
return _toHeadingStyle(editorState, textNode, selection);
}
return KeyEventResult.ignored;
};
@ -196,7 +195,7 @@ KeyEventResult _toHeadingStyle(
int _countOfSign(String text, Selection selection) {
for (var i = 6; i >= 0; i--) {
if (text.substring(0, selection.end.offset).startsWith('#' * i)) {
if (text.substring(0, selection.end.offset).contains('#' * i)) {
return i;
}
}

View File

@ -174,5 +174,45 @@ void main() async {
expect(textNode.subtype, BuiltInAttributeKey.bulletedList);
}
});
testWidgets('Presses whitespace key in edge cases', (tester) async {
const text = '';
final editor = tester.editor..insertTextNode(text);
await editor.startTesting();
final textNode = editor.nodeAtPath([0]) as TextNode;
await editor.updateSelection(
Selection.single(path: [0], startOffset: 0),
);
await editor.insertText(textNode, '*', 0);
await editor.pressLogicKey(LogicalKeyboardKey.space);
expect(textNode.subtype, BuiltInAttributeKey.bulletedList);
await editor.insertText(textNode, '[]', 0);
await editor.pressLogicKey(LogicalKeyboardKey.space);
expect(textNode.subtype, BuiltInAttributeKey.checkbox);
expect(textNode.attributes.check, false);
await editor.insertText(textNode, '1.', 0);
await editor.pressLogicKey(LogicalKeyboardKey.space);
expect(textNode.subtype, BuiltInAttributeKey.numberList);
await editor.insertText(textNode, '#', 0);
await editor.pressLogicKey(LogicalKeyboardKey.space);
expect(textNode.subtype, BuiltInAttributeKey.heading);
await editor.insertText(textNode, '[x]', 0);
await editor.pressLogicKey(LogicalKeyboardKey.space);
expect(textNode.subtype, BuiltInAttributeKey.checkbox);
expect(textNode.attributes.check, true);
const insertedText = '[]AppFlowy';
await editor.insertText(textNode, insertedText, 0);
await editor.pressLogicKey(LogicalKeyboardKey.space);
expect(textNode.subtype, BuiltInAttributeKey.checkbox);
expect(textNode.attributes.check, true);
expect(textNode.toRawString(), insertedText);
});
});
}