mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
commit
42fe2f675a
@ -21,7 +21,6 @@ class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
theme: ThemeData(
|
||||
// This is the theme of your application.
|
||||
//
|
||||
@ -64,12 +63,10 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
// Here we take the value from the MyHomePage object that was created by
|
||||
// the App.build method, and use it to set our appbar title.
|
||||
title: Text(widget.title),
|
||||
body: Container(
|
||||
alignment: Alignment.topCenter,
|
||||
child: _buildBody(),
|
||||
),
|
||||
body: _buildBody(),
|
||||
floatingActionButton: _buildExpandableFab(),
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'dart:collection';
|
||||
import 'package:flowy_editor/src/document/path.dart';
|
||||
import 'package:flowy_editor/src/document/text_delta.dart';
|
||||
import 'package:flowy_editor/src/operation/operation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import './attributes.dart';
|
||||
|
||||
@ -182,12 +181,12 @@ class TextNode extends Node {
|
||||
}) : _delta = delta,
|
||||
super(children: children ?? LinkedList(), attributes: attributes ?? {});
|
||||
|
||||
TextNode.empty()
|
||||
TextNode.empty({Attributes? attributes})
|
||||
: _delta = Delta([TextInsert('')]),
|
||||
super(
|
||||
type: 'text',
|
||||
children: LinkedList(),
|
||||
attributes: {},
|
||||
attributes: attributes ?? {},
|
||||
);
|
||||
|
||||
Delta get delta {
|
||||
|
@ -1,3 +1,5 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
@ -17,7 +19,7 @@ class FlowyRichText extends StatefulWidget {
|
||||
const FlowyRichText({
|
||||
Key? key,
|
||||
this.cursorHeight,
|
||||
this.cursorWidth = 2.0,
|
||||
this.cursorWidth = 1.0,
|
||||
this.textSpanDecorator,
|
||||
this.placeholderText = ' ',
|
||||
this.placeholderTextSpanDecorator,
|
||||
@ -41,7 +43,7 @@ class _FlowyRichTextState extends State<FlowyRichText> with Selectable {
|
||||
final _textKey = GlobalKey();
|
||||
final _placeholderTextKey = GlobalKey();
|
||||
|
||||
final lineHeight = 1.5;
|
||||
final _lineHeight = 1.5;
|
||||
|
||||
RenderParagraph get _renderParagraph =>
|
||||
_textKey.currentContext?.findRenderObject() as RenderParagraph;
|
||||
@ -69,13 +71,15 @@ class _FlowyRichTextState extends State<FlowyRichText> with Selectable {
|
||||
final cursorHeight = widget.cursorHeight ??
|
||||
_renderParagraph.getFullHeightForCaret(textPosition) ??
|
||||
_placeholderRenderParagraph.getFullHeightForCaret(textPosition) ??
|
||||
18.0; // default height
|
||||
return Rect.fromLTWH(
|
||||
16.0; // default height
|
||||
|
||||
final rect = Rect.fromLTWH(
|
||||
cursorOffset.dx - (widget.cursorWidth / 2),
|
||||
cursorOffset.dy,
|
||||
widget.cursorWidth,
|
||||
cursorHeight,
|
||||
);
|
||||
return rect;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -105,7 +109,7 @@ class _FlowyRichTextState extends State<FlowyRichText> with Selectable {
|
||||
extentOffset: selection.end.offset,
|
||||
);
|
||||
return _renderParagraph
|
||||
.getBoxesForSelection(textSelection)
|
||||
.getBoxesForSelection(textSelection, boxHeightStyle: BoxHeightStyle.max)
|
||||
.map((box) => box.toRect())
|
||||
.toList();
|
||||
}
|
||||
@ -138,24 +142,13 @@ class _FlowyRichTextState extends State<FlowyRichText> with Selectable {
|
||||
}
|
||||
|
||||
Widget _buildPlaceholderText(BuildContext context) {
|
||||
final textSpan = TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: widget.placeholderText,
|
||||
style: TextStyle(
|
||||
color: widget.textNode.toRawString().isNotEmpty
|
||||
? Colors.transparent
|
||||
: Colors.grey,
|
||||
fontSize: baseFontSize,
|
||||
height: lineHeight,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
final textSpan = _placeholderTextSpan;
|
||||
return RichText(
|
||||
key: _placeholderTextKey,
|
||||
text: widget.placeholderTextSpanDecorator != null
|
||||
? widget.placeholderTextSpanDecorator!(textSpan)
|
||||
textHeightBehavior: const TextHeightBehavior(
|
||||
applyHeightToFirstAscent: false, applyHeightToLastDescent: false),
|
||||
text: widget.textSpanDecorator != null
|
||||
? widget.textSpanDecorator!(textSpan)
|
||||
: textSpan,
|
||||
);
|
||||
}
|
||||
@ -164,6 +157,8 @@ class _FlowyRichTextState extends State<FlowyRichText> with Selectable {
|
||||
final textSpan = _textSpan;
|
||||
return RichText(
|
||||
key: _textKey,
|
||||
textHeightBehavior: const TextHeightBehavior(
|
||||
applyHeightToFirstAscent: false, applyHeightToLastDescent: false),
|
||||
text: widget.textSpanDecorator != null
|
||||
? widget.textSpanDecorator!(textSpan)
|
||||
: textSpan,
|
||||
@ -203,8 +198,18 @@ class _FlowyRichTextState extends State<FlowyRichText> with Selectable {
|
||||
.map((insert) => RichTextStyle(
|
||||
attributes: insert.attributes ?? {},
|
||||
text: insert.content,
|
||||
height: lineHeight,
|
||||
height: _lineHeight,
|
||||
).toTextSpan())
|
||||
.toList(growable: false),
|
||||
);
|
||||
|
||||
TextSpan get _placeholderTextSpan => TextSpan(children: [
|
||||
RichTextStyle(
|
||||
text: widget.placeholderText,
|
||||
attributes: {
|
||||
StyleKey.color: '0xFF707070',
|
||||
},
|
||||
height: _lineHeight,
|
||||
).toTextSpan()
|
||||
]);
|
||||
}
|
||||
|
@ -192,17 +192,7 @@ class RichTextStyle {
|
||||
TextSpan toTextSpan() => _toTextSpan(height);
|
||||
|
||||
double get topPadding {
|
||||
if (height == 1.0) {
|
||||
return 0;
|
||||
}
|
||||
// TODO: Need to be optimized.
|
||||
final painter =
|
||||
TextPainter(text: _toTextSpan(height), textDirection: TextDirection.ltr)
|
||||
..layout();
|
||||
final basePainter =
|
||||
TextPainter(text: _toTextSpan(null), textDirection: TextDirection.ltr)
|
||||
..layout();
|
||||
return painter.height - basePainter.height;
|
||||
return 0;
|
||||
}
|
||||
|
||||
TextSpan _toTextSpan(double? height) {
|
||||
|
@ -4,9 +4,64 @@ import 'package:flowy_editor/src/document/position.dart';
|
||||
import 'package:flowy_editor/src/document/selection.dart';
|
||||
import 'package:flowy_editor/src/editor_state.dart';
|
||||
import 'package:flowy_editor/src/extensions/text_node_extensions.dart';
|
||||
import 'package:flowy_editor/src/extensions/path_extensions.dart';
|
||||
import 'package:flowy_editor/src/operation/transaction_builder.dart';
|
||||
import 'package:flowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||
|
||||
void insertHeadingAfterSelection(EditorState editorState, String heading) {
|
||||
insertTextNodeAfterSelection(editorState, {
|
||||
StyleKey.subtype: StyleKey.heading,
|
||||
StyleKey.heading: heading,
|
||||
});
|
||||
}
|
||||
|
||||
void insertQuoteAfterSelection(EditorState editorState) {
|
||||
insertTextNodeAfterSelection(editorState, {
|
||||
StyleKey.subtype: StyleKey.quote,
|
||||
});
|
||||
}
|
||||
|
||||
void insertCheckboxAfterSelection(EditorState editorState) {
|
||||
insertTextNodeAfterSelection(editorState, {
|
||||
StyleKey.subtype: StyleKey.checkbox,
|
||||
StyleKey.checkbox: false,
|
||||
});
|
||||
}
|
||||
|
||||
void insertBulletedListAfterSelection(EditorState editorState) {
|
||||
insertTextNodeAfterSelection(editorState, {
|
||||
StyleKey.subtype: StyleKey.bulletedList,
|
||||
});
|
||||
}
|
||||
|
||||
bool insertTextNodeAfterSelection(
|
||||
EditorState editorState, Attributes attributes) {
|
||||
final selection = editorState.service.selectionService.currentSelection.value;
|
||||
final nodes = editorState.service.selectionService.currentSelectedNodes;
|
||||
if (selection == null || nodes.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final node = nodes.first;
|
||||
if (node is TextNode && node.delta.length == 0) {
|
||||
formatTextNodes(editorState, attributes);
|
||||
} else {
|
||||
final next = selection.end.path.next;
|
||||
final builder = TransactionBuilder(editorState);
|
||||
builder
|
||||
..insertNode(
|
||||
next,
|
||||
TextNode.empty(attributes: attributes),
|
||||
)
|
||||
..afterSelection = Selection.collapsed(
|
||||
Position(path: next, offset: 0),
|
||||
)
|
||||
..commit();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void formatText(EditorState editorState) {
|
||||
formatTextNodes(editorState, {});
|
||||
}
|
||||
|
@ -14,43 +14,56 @@ import 'package:flutter/services.dart';
|
||||
final List<PopupListItem> _popupListItems = [
|
||||
PopupListItem(
|
||||
text: 'Text',
|
||||
keywords: ['text'],
|
||||
icon: _popupListIcon('text'),
|
||||
handler: (editorState) => formatText(editorState),
|
||||
handler: (editorState) {
|
||||
insertTextNodeAfterSelection(editorState, {});
|
||||
},
|
||||
),
|
||||
PopupListItem(
|
||||
text: 'Heading 1',
|
||||
keywords: ['h1', 'heading 1'],
|
||||
icon: _popupListIcon('h1'),
|
||||
handler: (editorState) => formatHeading(editorState, StyleKey.h1),
|
||||
handler: (editorState) =>
|
||||
insertHeadingAfterSelection(editorState, StyleKey.h1),
|
||||
),
|
||||
PopupListItem(
|
||||
text: 'Heading 2',
|
||||
keywords: ['h2', 'heading 2'],
|
||||
icon: _popupListIcon('h2'),
|
||||
handler: (editorState) => formatHeading(editorState, StyleKey.h2),
|
||||
handler: (editorState) =>
|
||||
insertHeadingAfterSelection(editorState, StyleKey.h2),
|
||||
),
|
||||
PopupListItem(
|
||||
text: 'Heading 3',
|
||||
keywords: ['h3', 'heading 3'],
|
||||
icon: _popupListIcon('h3'),
|
||||
handler: (editorState) => formatHeading(editorState, StyleKey.h3),
|
||||
handler: (editorState) =>
|
||||
insertHeadingAfterSelection(editorState, StyleKey.h3),
|
||||
),
|
||||
PopupListItem(
|
||||
text: 'Bullets',
|
||||
text: 'Bulleted List',
|
||||
keywords: ['bulleted list'],
|
||||
icon: _popupListIcon('bullets'),
|
||||
handler: (editorState) => formatBulletedList(editorState),
|
||||
handler: (editorState) => insertBulletedListAfterSelection(editorState),
|
||||
),
|
||||
// PopupListItem(
|
||||
// text: 'Numbered list',
|
||||
// keywords: ['numbered list'],
|
||||
// icon: _popupListIcon('number'),
|
||||
// handler: (editorState) => debugPrint('Not implement yet!'),
|
||||
// ),
|
||||
PopupListItem(
|
||||
text: 'Numbered list',
|
||||
icon: _popupListIcon('number'),
|
||||
handler: (editorState) => debugPrint('Not implement yet!'),
|
||||
),
|
||||
PopupListItem(
|
||||
text: 'Checkboxes',
|
||||
text: 'To-do List',
|
||||
keywords: ['checkbox', 'todo'],
|
||||
icon: _popupListIcon('checkbox'),
|
||||
handler: (editorState) => formatCheckbox(editorState),
|
||||
handler: (editorState) => insertCheckboxAfterSelection(editorState),
|
||||
),
|
||||
];
|
||||
|
||||
OverlayEntry? _popupListOverlay;
|
||||
EditorState? _editorState;
|
||||
bool _selectionChangeBySlash = false;
|
||||
FlowyKeyEventHandler slashShortcutHandler = (editorState, event) {
|
||||
if (event.logicalKey != LogicalKeyboardKey.slash) {
|
||||
return KeyEventResult.ignored;
|
||||
@ -69,21 +82,19 @@ FlowyKeyEventHandler slashShortcutHandler = (editorState, event) {
|
||||
if (selection == null || context == null || selectable == null) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
final rect = selectable.getCursorRectInPosition(selection.start);
|
||||
if (rect == null) {
|
||||
final selectionRects = editorState.service.selectionService.selectionRects;
|
||||
if (selectionRects.isEmpty) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
final offset = selectable.localToGlobal(rect.topLeft);
|
||||
|
||||
TransactionBuilder(editorState)
|
||||
..replaceText(textNode, selection.start.offset,
|
||||
selection.end.offset - selection.start.offset, '/')
|
||||
selection.end.offset - selection.start.offset, event.character ?? '')
|
||||
..commit();
|
||||
|
||||
_editorState = editorState;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopupList(context, editorState, offset);
|
||||
_selectionChangeBySlash = false;
|
||||
showPopupList(context, editorState, selectionRects.first.bottomRight);
|
||||
});
|
||||
|
||||
return KeyEventResult.handled;
|
||||
@ -94,8 +105,8 @@ void showPopupList(
|
||||
_popupListOverlay?.remove();
|
||||
_popupListOverlay = OverlayEntry(
|
||||
builder: (context) => Positioned(
|
||||
top: offset.dy + 15.0,
|
||||
left: offset.dx + 5.0,
|
||||
top: offset.dy,
|
||||
left: offset.dx,
|
||||
child: PopupListWidget(
|
||||
editorState: editorState,
|
||||
items: _popupListItems,
|
||||
@ -117,6 +128,15 @@ void clearPopupList() {
|
||||
if (_popupListOverlay == null || _editorState == null) {
|
||||
return;
|
||||
}
|
||||
final selection =
|
||||
_editorState?.service.selectionService.currentSelection.value;
|
||||
if (selection == null) {
|
||||
return;
|
||||
}
|
||||
if (_selectionChangeBySlash) {
|
||||
_selectionChangeBySlash = false;
|
||||
return;
|
||||
}
|
||||
_popupListOverlay?.remove();
|
||||
_popupListOverlay = null;
|
||||
|
||||
@ -142,21 +162,55 @@ class PopupListWidget extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _PopupListWidgetState extends State<PopupListWidget> {
|
||||
final focusNode = FocusNode(debugLabel: 'popup_list_widget');
|
||||
var selectedIndex = 0;
|
||||
final _focusNode = FocusNode(debugLabel: 'popup_list_widget');
|
||||
int _selectedIndex = 0;
|
||||
List<PopupListItem> _items = [];
|
||||
|
||||
int _maxKeywordLength = 0;
|
||||
|
||||
String __keyword = '';
|
||||
String get _keyword => __keyword;
|
||||
set _keyword(String keyword) {
|
||||
__keyword = keyword;
|
||||
|
||||
final items = widget.items
|
||||
.where((item) =>
|
||||
item.keywords.any((keyword) => keyword.contains(_keyword)))
|
||||
.toList(growable: false);
|
||||
if (items.isNotEmpty) {
|
||||
var maxKeywordLength = 0;
|
||||
for (var item in _items) {
|
||||
for (var keyword in item.keywords) {
|
||||
maxKeywordLength = max(maxKeywordLength, keyword.length);
|
||||
}
|
||||
}
|
||||
_maxKeywordLength = maxKeywordLength;
|
||||
}
|
||||
|
||||
if (keyword.length >= _maxKeywordLength + 2) {
|
||||
clearPopupList();
|
||||
} else {
|
||||
setState(() {
|
||||
_selectedIndex = 0;
|
||||
_items = items;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_items = widget.items;
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
focusNode.requestFocus();
|
||||
_focusNode.requestFocus();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
focusNode.dispose();
|
||||
_focusNode.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
@ -164,7 +218,7 @@ class _PopupListWidgetState extends State<PopupListWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Focus(
|
||||
focusNode: focusNode,
|
||||
focusNode: _focusNode,
|
||||
onKey: _onKey,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
@ -178,9 +232,26 @@ class _PopupListWidgetState extends State<PopupListWidget> {
|
||||
],
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: _buildColumns(widget.items, selectedIndex),
|
||||
child: _items.isEmpty
|
||||
? _buildNoResultsWidget(context)
|
||||
: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: _buildColumns(_items, _selectedIndex),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNoResultsWidget(BuildContext context) {
|
||||
return const Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Material(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(12.0),
|
||||
child: Text(
|
||||
'No results',
|
||||
style: TextStyle(color: Colors.grey),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -214,26 +285,43 @@ class _PopupListWidgetState extends State<PopupListWidget> {
|
||||
}
|
||||
|
||||
KeyEventResult _onKey(FocusNode node, RawKeyEvent event) {
|
||||
debugPrint('slash on key $event');
|
||||
if (event is! RawKeyDownEvent) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
final arrowKeys = [
|
||||
LogicalKeyboardKey.arrowLeft,
|
||||
LogicalKeyboardKey.arrowRight,
|
||||
LogicalKeyboardKey.arrowUp,
|
||||
LogicalKeyboardKey.arrowDown
|
||||
];
|
||||
|
||||
if (event.logicalKey == LogicalKeyboardKey.enter) {
|
||||
if (0 <= selectedIndex && selectedIndex < widget.items.length) {
|
||||
_deleteSlash();
|
||||
widget.items[selectedIndex].handler(widget.editorState);
|
||||
if (0 <= _selectedIndex && _selectedIndex < _items.length) {
|
||||
_deleteLastCharacters(length: _keyword.length + 1);
|
||||
_items[_selectedIndex].handler(widget.editorState);
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
} else if (event.logicalKey == LogicalKeyboardKey.escape) {
|
||||
clearPopupList();
|
||||
return KeyEventResult.handled;
|
||||
} else if (event.logicalKey == LogicalKeyboardKey.backspace) {
|
||||
clearPopupList();
|
||||
_deleteSlash();
|
||||
if (_keyword.isEmpty) {
|
||||
clearPopupList();
|
||||
} else {
|
||||
_keyword = _keyword.substring(0, _keyword.length - 1);
|
||||
}
|
||||
_deleteLastCharacters();
|
||||
return KeyEventResult.handled;
|
||||
} else if (event.character != null &&
|
||||
!arrowKeys.contains(event.logicalKey)) {
|
||||
_keyword += event.character!;
|
||||
_insertText(event.character!);
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
|
||||
var newSelectedIndex = selectedIndex;
|
||||
var newSelectedIndex = _selectedIndex;
|
||||
if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
|
||||
newSelectedIndex -= widget.maxItemInRow;
|
||||
} else if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
|
||||
@ -243,26 +331,44 @@ class _PopupListWidgetState extends State<PopupListWidget> {
|
||||
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
|
||||
newSelectedIndex += 1;
|
||||
}
|
||||
if (newSelectedIndex != selectedIndex) {
|
||||
if (newSelectedIndex != _selectedIndex) {
|
||||
setState(() {
|
||||
selectedIndex = max(0, min(widget.items.length - 1, newSelectedIndex));
|
||||
_selectedIndex = max(0, min(_items.length - 1, newSelectedIndex));
|
||||
});
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
void _deleteSlash() {
|
||||
void _deleteLastCharacters({int length = 1}) {
|
||||
final selection =
|
||||
widget.editorState.service.selectionService.currentSelection.value;
|
||||
final nodes =
|
||||
widget.editorState.service.selectionService.currentSelectedNodes;
|
||||
if (selection != null && nodes.length == 1) {
|
||||
_selectionChangeBySlash = true;
|
||||
TransactionBuilder(widget.editorState)
|
||||
..deleteText(
|
||||
nodes.first as TextNode,
|
||||
selection.start.offset - 1,
|
||||
1,
|
||||
selection.start.offset - length,
|
||||
length,
|
||||
)
|
||||
..commit();
|
||||
}
|
||||
}
|
||||
|
||||
void _insertText(String text) {
|
||||
final selection =
|
||||
widget.editorState.service.selectionService.currentSelection.value;
|
||||
final nodes =
|
||||
widget.editorState.service.selectionService.currentSelectedNodes;
|
||||
if (selection != null && nodes.length == 1) {
|
||||
_selectionChangeBySlash = true;
|
||||
TransactionBuilder(widget.editorState)
|
||||
..insertText(
|
||||
nodes.first as TextNode,
|
||||
selection.end.offset,
|
||||
text,
|
||||
)
|
||||
..commit();
|
||||
}
|
||||
@ -318,12 +424,14 @@ class _PopupListItemWidget extends StatelessWidget {
|
||||
class PopupListItem {
|
||||
PopupListItem({
|
||||
required this.text,
|
||||
required this.keywords,
|
||||
this.message = '',
|
||||
required this.icon,
|
||||
required this.handler,
|
||||
});
|
||||
|
||||
final String text;
|
||||
final List<String> keywords;
|
||||
final String message;
|
||||
final Widget icon;
|
||||
final void Function(EditorState editorState) handler;
|
||||
|
@ -39,8 +39,8 @@ FlowyKeyEventHandler whiteSpaceHandler = (editorState, event) {
|
||||
return _toCheckboxList(editorState, textNode);
|
||||
} else if (_bulletedListSymbols.any(text.startsWith)) {
|
||||
return _toBulletedList(editorState, textNode);
|
||||
} else if (_countOfSign(text) != 0) {
|
||||
return _toHeadingStyle(editorState, textNode);
|
||||
} else if (_countOfSign(text, selection) != 0) {
|
||||
return _toHeadingStyle(editorState, textNode, selection);
|
||||
}
|
||||
|
||||
return KeyEventResult.ignored;
|
||||
@ -99,8 +99,12 @@ KeyEventResult _toCheckboxList(EditorState editorState, TextNode textNode) {
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
|
||||
KeyEventResult _toHeadingStyle(EditorState editorState, TextNode textNode) {
|
||||
final x = _countOfSign(textNode.toRawString());
|
||||
KeyEventResult _toHeadingStyle(
|
||||
EditorState editorState, TextNode textNode, Selection selection) {
|
||||
final x = _countOfSign(
|
||||
textNode.toRawString(),
|
||||
selection,
|
||||
);
|
||||
final hX = 'h$x';
|
||||
if (textNode.attributes.heading == hX) {
|
||||
return KeyEventResult.ignored;
|
||||
@ -121,9 +125,9 @@ KeyEventResult _toHeadingStyle(EditorState editorState, TextNode textNode) {
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
|
||||
int _countOfSign(String text) {
|
||||
int _countOfSign(String text, Selection selection) {
|
||||
for (var i = 6; i >= 0; i--) {
|
||||
if (text.startsWith('#' * i)) {
|
||||
if (text.substring(0, selection.end.offset).startsWith('#' * i)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user