Merge pull request #786 from LucasXu0/feat/markdown_input

implement markdown input style, like, #, *, -, -[]
This commit is contained in:
Nathan.fooo 2022-08-08 18:47:16 +08:00 committed by GitHub
commit a138b32c8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 166 additions and 23 deletions

View File

@ -16,6 +16,7 @@ import 'package:flowy_editor/service/internal_key_event_handlers/delete_text_han
import 'package:flowy_editor/service/internal_key_event_handlers/enter_in_edge_of_text_node_handler.dart';
import 'package:flowy_editor/service/internal_key_event_handlers/slash_handler.dart';
import 'package:flowy_editor/service/internal_key_event_handlers/update_text_style_by_command_x_handler.dart';
import 'package:flowy_editor/service/internal_key_event_handlers/whitespace_handler.dart';
import 'package:flowy_editor/service/keyboard_service.dart';
import 'package:flowy_editor/service/render_plugin_service.dart';
import 'package:flowy_editor/service/scroll_service.dart';
@ -40,6 +41,7 @@ List<FlowyKeyEventHandler> defaultKeyEventHandler = [
copyPasteKeysHandler,
enterInEdgeOfTextNodeHandler,
updateTextStyleByCommandXHandler,
whiteSpaceHandler,
];
class FlowyEditor extends StatefulWidget {

View File

@ -1,8 +1,9 @@
import 'package:flowy_editor/flowy_editor.dart';
import 'package:flowy_editor/service/keyboard_service.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flowy_editor/flowy_editor.dart';
import 'package:flowy_editor/service/keyboard_service.dart';
// Handle delete text.
FlowyKeyEventHandler deleteTextHandler = (editorState, event) {
if (event.logicalKey != LogicalKeyboardKey.backspace) {
@ -28,9 +29,16 @@ FlowyKeyEventHandler deleteTextHandler = (editorState, event) {
if (index < 0) {
// 1. style
if (textNode.subtype != null) {
transactionBuilder.updateNode(textNode, {
'subtype': null,
});
transactionBuilder
..updateNode(textNode, {
'subtype': null,
})
..afterSelection = Selection.collapsed(
Position(
path: textNode.path,
offset: 0,
),
);
} else {
// 2. non-style
// find previous text node.

View File

@ -72,16 +72,22 @@ FlowyKeyEventHandler slashShortcutHandler = (editorState, event) {
final rect = selectable.getCursorRectInPosition(selection.start);
final offset = selectable.localToGlobal(rect.topLeft);
if (!selection.isCollapsed) {
TransactionBuilder(editorState)
..deleteText(
textNode,
selection.start.offset,
selection.end.offset - selection.start.offset,
)
..commit();
}
TransactionBuilder(editorState)
..replaceText(textNode, selection.start.offset,
selection.end.offset - selection.start.offset, '/')
..commit();
_editorState = editorState;
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopupList(context, editorState, offset);
});
return KeyEventResult.handled;
};
void showPopupList(
BuildContext context, EditorState editorState, Offset offset) {
_popupListOverlay?.remove();
_popupListOverlay = OverlayEntry(
builder: (context) => Positioned(
@ -97,16 +103,12 @@ FlowyKeyEventHandler slashShortcutHandler = (editorState, event) {
Overlay.of(context)?.insert(_popupListOverlay!);
editorState.service.selectionService.currentSelection
.removeListener(clearPopupListOverlay);
.removeListener(clearPopupList);
editorState.service.selectionService.currentSelection
.addListener(clearPopupListOverlay);
// editorState.service.keyboardService?.disable();
_editorState = editorState;
.addListener(clearPopupList);
}
return KeyEventResult.handled;
};
void clearPopupListOverlay() {
void clearPopupList() {
_popupListOverlay?.remove();
_popupListOverlay = null;
@ -215,7 +217,7 @@ class _PopupListWidgetState extends State<PopupListWidget> {
}
if (event.logicalKey == LogicalKeyboardKey.escape) {
clearPopupListOverlay();
clearPopupList();
return KeyEventResult.handled;
}

View File

@ -0,0 +1,131 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flowy_editor/document/node.dart';
import 'package:flowy_editor/document/position.dart';
import 'package:flowy_editor/document/selection.dart';
import 'package:flowy_editor/editor_state.dart';
import 'package:flowy_editor/operation/transaction_builder.dart';
import 'package:flowy_editor/render/rich_text/rich_text_style.dart';
import 'package:flowy_editor/service/keyboard_service.dart';
const _bulletedListSymbols = ['*', '-'];
const _checkboxListSymbols = ['[x]', '-[x]'];
const _unCheckboxListSymbols = ['[]', '-[]'];
FlowyKeyEventHandler whiteSpaceHandler = (editorState, event) {
if (event.logicalKey != LogicalKeyboardKey.space) {
return KeyEventResult.ignored;
}
/// Process markdown input style.
///
/// like, #, *, -, 1., -[],
final selection = editorState.service.selectionService.currentSelection.value;
if (selection == null || !selection.isCollapsed) {
return KeyEventResult.ignored;
}
final textNodes = editorState.service.selectionService.currentSelectedNodes
.whereType<TextNode>();
if (textNodes.length != 1) {
return KeyEventResult.ignored;
}
final textNode = textNodes.first;
final text = textNode.toRawString();
if ((_checkboxListSymbols + _unCheckboxListSymbols).any(text.startsWith)) {
return _toCheckboxList(editorState, textNode);
} else if (_bulletedListSymbols.any(text.startsWith)) {
return _toBulletedList(editorState, textNode);
} else if (_countOfSign(text) != 0) {
return _toHeadingStyle(editorState, textNode);
}
return KeyEventResult.ignored;
};
KeyEventResult _toBulletedList(EditorState editorState, TextNode textNode) {
if (textNode.subtype == StyleKey.bulletedList) {
return KeyEventResult.ignored;
}
TransactionBuilder(editorState)
..deleteText(textNode, 0, 1)
..updateNode(textNode, {
StyleKey.subtype: StyleKey.bulletedList,
})
..afterSelection = Selection.collapsed(
Position(
path: textNode.path,
offset: 0,
),
)
..commit();
return KeyEventResult.handled;
}
KeyEventResult _toCheckboxList(EditorState editorState, TextNode textNode) {
if (textNode.subtype == StyleKey.checkbox) {
return KeyEventResult.ignored;
}
final String symbol;
bool check = false;
final symbols = List<String>.from(_checkboxListSymbols)
..retainWhere(textNode.toRawString().startsWith);
if (symbols.isNotEmpty) {
symbol = symbols.first;
check = true;
} else {
symbol = (List<String>.from(_unCheckboxListSymbols)
..retainWhere(textNode.toRawString().startsWith))
.first;
check = false;
}
TransactionBuilder(editorState)
..deleteText(textNode, 0, symbol.length)
..updateNode(textNode, {
StyleKey.subtype: StyleKey.checkbox,
StyleKey.checkbox: check,
})
..afterSelection = Selection.collapsed(
Position(
path: textNode.path,
offset: 0,
),
)
..commit();
return KeyEventResult.handled;
}
KeyEventResult _toHeadingStyle(EditorState editorState, TextNode textNode) {
final x = _countOfSign(textNode.toRawString());
final hX = 'h$x';
if (textNode.attributes.heading == hX) {
return KeyEventResult.ignored;
}
TransactionBuilder(editorState)
..deleteText(textNode, 0, x)
..updateNode(textNode, {
StyleKey.subtype: StyleKey.heading,
StyleKey.heading: hX,
})
..afterSelection = Selection.collapsed(
Position(
path: textNode.path,
offset: 0,
),
)
..commit();
return KeyEventResult.handled;
}
int _countOfSign(String text) {
for (var i = 6; i >= 0; i--) {
if (text.startsWith('#' * i)) {
return i;
}
}
return 0;
}