Merge remote-tracking branch 'origin/main' into enzoftware/main
# Conflicts: # frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/tex_block_node_widget.dart
This commit is contained in:
commit
017733856f
22 changed files with 364 additions and 58 deletions
|
|
@ -1,3 +1,13 @@
|
|||
## 0.0.6
|
||||
* Add three plugins: Code Block, LateX, and Horizontal rule.
|
||||
* Support web platform.
|
||||
* Support more markdown syntax conversions.
|
||||
* `~ ~` to format text as strikethrough
|
||||
* `_ _` to format text as italic
|
||||
* \` \` to format text as code
|
||||
* `[]()` to format text as link
|
||||
* Fix some bugs.
|
||||
|
||||
## 0.0.5
|
||||
* Support customize the hotkeys for a shortcut on different platforms.
|
||||
* Support customize a theme.
|
||||
|
|
|
|||
|
|
@ -9,12 +9,6 @@
|
|||
"align": "center"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "tex",
|
||||
"attributes": {
|
||||
"tex": "x = 2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"attributes": { "subtype": "heading", "heading": "h1" },
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:example/plugin/code_block_node_widget.dart';
|
||||
import 'package:example/plugin/horizontal_rule_node_widget.dart';
|
||||
import 'package:example/plugin/tex_block_node_widget.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -121,15 +122,18 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||
customBuilders: {
|
||||
'text/code_block': CodeBlockNodeWidgetBuilder(),
|
||||
'tex': TeXBlockNodeWidgetBuidler(),
|
||||
'horizontal_rule': HorizontalRuleWidgetBuilder(),
|
||||
},
|
||||
shortcutEvents: [
|
||||
enterInCodeBlock,
|
||||
ignoreKeysInCodeBlock,
|
||||
underscoreToItalic,
|
||||
insertHorizontalRule,
|
||||
],
|
||||
selectionMenuItems: [
|
||||
codeBlockItem,
|
||||
codeBlockMenuItem,
|
||||
teXBlockMenuItem,
|
||||
horizontalRuleMenuItem,
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -44,9 +44,13 @@ ShortcutEventHandler _ignorekHandler = (editorState, event) {
|
|||
return KeyEventResult.ignored;
|
||||
};
|
||||
|
||||
SelectionMenuItem codeBlockItem = SelectionMenuItem(
|
||||
SelectionMenuItem codeBlockMenuItem = SelectionMenuItem(
|
||||
name: () => 'Code Block',
|
||||
icon: const Icon(Icons.abc),
|
||||
icon: const Icon(
|
||||
Icons.abc,
|
||||
color: Colors.black,
|
||||
size: 18.0,
|
||||
),
|
||||
keywords: ['code block'],
|
||||
handler: (editorState, _, __) {
|
||||
final selection =
|
||||
|
|
|
|||
|
|
@ -0,0 +1,167 @@
|
|||
import 'dart:collection';
|
||||
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
ShortcutEvent insertHorizontalRule = ShortcutEvent(
|
||||
key: 'Horizontal rule',
|
||||
command: 'Minus',
|
||||
handler: _insertHorzaontalRule,
|
||||
);
|
||||
|
||||
ShortcutEventHandler _insertHorzaontalRule = (editorState, event) {
|
||||
final selection = editorState.service.selectionService.currentSelection.value;
|
||||
final textNodes = editorState.service.selectionService.currentSelectedNodes
|
||||
.whereType<TextNode>();
|
||||
if (textNodes.length != 1 || selection == null) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
final textNode = textNodes.first;
|
||||
if (textNode.toRawString() == '--') {
|
||||
TransactionBuilder(editorState)
|
||||
..deleteText(textNode, 0, 2)
|
||||
..insertNode(
|
||||
textNode.path,
|
||||
Node(
|
||||
type: 'horizontal_rule',
|
||||
children: LinkedList(),
|
||||
attributes: {},
|
||||
),
|
||||
)
|
||||
..afterSelection =
|
||||
Selection.single(path: textNode.path.next, startOffset: 0)
|
||||
..commit();
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
return KeyEventResult.ignored;
|
||||
};
|
||||
|
||||
SelectionMenuItem horizontalRuleMenuItem = SelectionMenuItem(
|
||||
name: () => 'Horizontal rule',
|
||||
icon: const Icon(
|
||||
Icons.horizontal_rule,
|
||||
color: Colors.black,
|
||||
size: 18.0,
|
||||
),
|
||||
keywords: ['horizontal rule'],
|
||||
handler: (editorState, _, __) {
|
||||
final selection =
|
||||
editorState.service.selectionService.currentSelection.value;
|
||||
final textNodes = editorState.service.selectionService.currentSelectedNodes
|
||||
.whereType<TextNode>();
|
||||
if (selection == null || textNodes.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final textNode = textNodes.first;
|
||||
if (textNode.toRawString().isEmpty) {
|
||||
TransactionBuilder(editorState)
|
||||
..insertNode(
|
||||
textNode.path,
|
||||
Node(
|
||||
type: 'horizontal_rule',
|
||||
children: LinkedList(),
|
||||
attributes: {},
|
||||
),
|
||||
)
|
||||
..afterSelection =
|
||||
Selection.single(path: textNode.path.next, startOffset: 0)
|
||||
..commit();
|
||||
} else {
|
||||
TransactionBuilder(editorState)
|
||||
..insertNode(
|
||||
selection.end.path.next,
|
||||
TextNode(
|
||||
type: 'text',
|
||||
children: LinkedList(),
|
||||
attributes: {
|
||||
'subtype': 'horizontal_rule',
|
||||
},
|
||||
delta: Delta()..insert('---'),
|
||||
),
|
||||
)
|
||||
..afterSelection = selection
|
||||
..commit();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
class HorizontalRuleWidgetBuilder extends NodeWidgetBuilder<Node> {
|
||||
@override
|
||||
Widget build(NodeWidgetContext<Node> context) {
|
||||
return _HorizontalRuleWidget(
|
||||
key: context.node.key,
|
||||
node: context.node,
|
||||
editorState: context.editorState,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
NodeValidator<Node> get nodeValidator => (node) {
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
class _HorizontalRuleWidget extends StatefulWidget {
|
||||
const _HorizontalRuleWidget({
|
||||
Key? key,
|
||||
required this.node,
|
||||
required this.editorState,
|
||||
}) : super(key: key);
|
||||
|
||||
final Node node;
|
||||
final EditorState editorState;
|
||||
|
||||
@override
|
||||
State<_HorizontalRuleWidget> createState() => __HorizontalRuleWidgetState();
|
||||
}
|
||||
|
||||
class __HorizontalRuleWidgetState extends State<_HorizontalRuleWidget>
|
||||
with SelectableMixin {
|
||||
RenderBox get _renderBox => context.findRenderObject() as RenderBox;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Container(
|
||||
height: 1,
|
||||
color: Colors.grey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Position start() => Position(path: widget.node.path, offset: 0);
|
||||
|
||||
@override
|
||||
Position end() => Position(path: widget.node.path, offset: 1);
|
||||
|
||||
@override
|
||||
Position getPositionInOffset(Offset start) => end();
|
||||
|
||||
@override
|
||||
bool get shouldCursorBlink => false;
|
||||
|
||||
@override
|
||||
CursorStyle get cursorStyle => CursorStyle.borderLine;
|
||||
|
||||
@override
|
||||
Rect? getCursorRectInPosition(Position position) {
|
||||
final size = _renderBox.size;
|
||||
return Rect.fromLTWH(-size.width / 2.0, 0, size.width, size.height);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Rect> getRectsInSelection(Selection selection) =>
|
||||
[Offset.zero & _renderBox.size];
|
||||
|
||||
@override
|
||||
Selection getSelectionInRange(Offset start, Offset end) => Selection.single(
|
||||
path: widget.node.path,
|
||||
startOffset: 0,
|
||||
endOffset: 1,
|
||||
);
|
||||
|
||||
@override
|
||||
Offset localToGlobal(Offset offset) => _renderBox.localToGlobal(offset);
|
||||
}
|
||||
|
|
@ -6,7 +6,11 @@ import 'package:flutter_math_fork/flutter_math.dart';
|
|||
|
||||
SelectionMenuItem teXBlockMenuItem = SelectionMenuItem(
|
||||
name: () => 'Tex',
|
||||
icon: const Icon(Icons.text_fields_rounded),
|
||||
icon: const Icon(
|
||||
Icons.text_fields_rounded,
|
||||
color: Colors.black,
|
||||
size: 18.0,
|
||||
),
|
||||
keywords: ['tex, latex, katex'],
|
||||
handler: (editorState, _, __) {
|
||||
final selection =
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ EXTERNAL SOURCES:
|
|||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
FlutterMacOS: ae6af50a8ea7d6103d888583d46bd8328a7e9811
|
||||
FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424
|
||||
path_provider_macos: 3c0c3b4b0d4a76d2bf989a913c2de869c5641a19
|
||||
rich_clipboard_macos: 43364b66b9dc69d203eb8dd6d758e2d12e02723c
|
||||
url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"@@locale": "ml_IN",
|
||||
"bold": "ബോൾഡ്",
|
||||
"@bold": {},
|
||||
"bulletedList": "ബുള്ളറ്റഡ് പട്ടിക",
|
||||
"@bulletedList": {},
|
||||
"checkbox": "ചെക്ക്ബോക്സ്",
|
||||
"@checkbox": {},
|
||||
"embedCode": "എംബെഡഡ് കോഡ്",
|
||||
"@embedCode": {},
|
||||
"heading1": "തലക്കെട്ട് 1",
|
||||
"@heading1": {},
|
||||
"heading2": "തലക്കെട്ട് 2",
|
||||
"@heading2": {},
|
||||
"heading3": "തലക്കെട്ട് 3",
|
||||
"@heading3": {},
|
||||
"highlight": "പ്രമുഖമാക്കിക്കാട്ടുക",
|
||||
"@highlight": {},
|
||||
"image": "ചിത്രം",
|
||||
"@image": {},
|
||||
"italic": "ഇറ്റാലിക്",
|
||||
"@italic": {},
|
||||
"link": "ലിങ്ക്",
|
||||
"@link": {},
|
||||
"numberedList": "അക്കമിട്ട പട്ടിക",
|
||||
"@numberedList": {},
|
||||
"quote": "ഉദ്ധരണി",
|
||||
"@quote": {},
|
||||
"strikethrough": "സ്ട്രൈക്ക്ത്രൂ",
|
||||
"@strikethrough": {},
|
||||
"text": "വചനം",
|
||||
"@text": {},
|
||||
"underline": "അടിവരയിടുക",
|
||||
"@underline": {}
|
||||
}
|
||||
|
|
@ -19,8 +19,9 @@ extension TextNodeExtension on TextNode {
|
|||
}
|
||||
final length = op.length;
|
||||
if (start < endOffset && start + length > startOffset) {
|
||||
if (op.attributes?.containsKey(styleKey) == true) {
|
||||
return op.attributes![styleKey];
|
||||
final attributes = op.attributes;
|
||||
if (attributes != null && attributes[styleKey] is T?) {
|
||||
return attributes[styleKey];
|
||||
}
|
||||
}
|
||||
start += length;
|
||||
|
|
@ -29,42 +30,40 @@ extension TextNodeExtension on TextNode {
|
|||
}
|
||||
|
||||
bool allSatisfyLinkInSelection(Selection selection) =>
|
||||
allSatisfyInSelection(selection, BuiltInAttributeKey.href, <bool>(value) {
|
||||
allSatisfyInSelection(selection, BuiltInAttributeKey.href, (value) {
|
||||
return value != null;
|
||||
});
|
||||
|
||||
bool allSatisfyBoldInSelection(Selection selection) =>
|
||||
allSatisfyInSelection(selection, BuiltInAttributeKey.bold, <bool>(value) {
|
||||
allSatisfyInSelection(selection, BuiltInAttributeKey.bold, (value) {
|
||||
return value == true;
|
||||
});
|
||||
|
||||
bool allSatisfyItalicInSelection(Selection selection) =>
|
||||
allSatisfyInSelection(selection, BuiltInAttributeKey.italic,
|
||||
<bool>(value) {
|
||||
allSatisfyInSelection(selection, BuiltInAttributeKey.italic, (value) {
|
||||
return value == true;
|
||||
});
|
||||
|
||||
bool allSatisfyUnderlineInSelection(Selection selection) =>
|
||||
allSatisfyInSelection(selection, BuiltInAttributeKey.underline,
|
||||
<bool>(value) {
|
||||
allSatisfyInSelection(selection, BuiltInAttributeKey.underline, (value) {
|
||||
return value == true;
|
||||
});
|
||||
|
||||
bool allSatisfyStrikethroughInSelection(Selection selection) =>
|
||||
allSatisfyInSelection(selection, BuiltInAttributeKey.strikethrough,
|
||||
<bool>(value) {
|
||||
(value) {
|
||||
return value == true;
|
||||
});
|
||||
|
||||
bool allSatisfyCodeInSelection(Selection selection) =>
|
||||
allSatisfyInSelection(selection, BuiltInAttributeKey.code, <bool>(value) {
|
||||
allSatisfyInSelection(selection, BuiltInAttributeKey.code, (value) {
|
||||
return value == true;
|
||||
});
|
||||
|
||||
bool allSatisfyInSelection(
|
||||
Selection selection,
|
||||
String styleKey,
|
||||
bool Function<T>(T value) test,
|
||||
bool Function(dynamic value) test,
|
||||
) {
|
||||
if (BuiltInAttributeKey.globalStyleKeys.contains(styleKey)) {
|
||||
if (attributes.containsKey(styleKey)) {
|
||||
|
|
@ -129,40 +128,40 @@ extension TextNodesExtension on List<TextNode> {
|
|||
bool allSatisfyBoldInSelection(Selection selection) => allSatisfyInSelection(
|
||||
selection,
|
||||
BuiltInAttributeKey.bold,
|
||||
<bool>(value) => value == true,
|
||||
(value) => value == true,
|
||||
);
|
||||
|
||||
bool allSatisfyItalicInSelection(Selection selection) =>
|
||||
allSatisfyInSelection(
|
||||
selection,
|
||||
BuiltInAttributeKey.italic,
|
||||
<bool>(value) => value == true,
|
||||
(value) => value == true,
|
||||
);
|
||||
|
||||
bool allSatisfyUnderlineInSelection(Selection selection) =>
|
||||
allSatisfyInSelection(
|
||||
selection,
|
||||
BuiltInAttributeKey.underline,
|
||||
<bool>(value) => value == true,
|
||||
(value) => value == true,
|
||||
);
|
||||
|
||||
bool allSatisfyStrikethroughInSelection(Selection selection) =>
|
||||
allSatisfyInSelection(
|
||||
selection,
|
||||
BuiltInAttributeKey.strikethrough,
|
||||
<bool>(value) => value == true,
|
||||
(value) => value == true,
|
||||
);
|
||||
|
||||
bool allSatisfyInSelection(
|
||||
Selection selection,
|
||||
String styleKey,
|
||||
bool Function<T>(T value) test,
|
||||
bool Function(dynamic value) test,
|
||||
) {
|
||||
if (isEmpty) {
|
||||
return false;
|
||||
}
|
||||
if (length == 1) {
|
||||
return first.allSatisfyInSelection(selection, styleKey, <bool>(value) {
|
||||
return first.allSatisfyInSelection(selection, styleKey, (value) {
|
||||
return test(value);
|
||||
});
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import 'messages_hu-HU.dart' as messages_hu_hu;
|
|||
import 'messages_id-ID.dart' as messages_id_id;
|
||||
import 'messages_it-IT.dart' as messages_it_it;
|
||||
import 'messages_ja-JP.dart' as messages_ja_jp;
|
||||
import 'messages_ml_IN.dart' as messages_ml_in;
|
||||
import 'messages_nl-NL.dart' as messages_nl_nl;
|
||||
import 'messages_pl-PL.dart' as messages_pl_pl;
|
||||
import 'messages_pt-BR.dart' as messages_pt_br;
|
||||
|
|
@ -48,6 +49,7 @@ Map<String, LibraryLoader> _deferredLibraries = {
|
|||
'id_ID': () => new Future.value(null),
|
||||
'it_IT': () => new Future.value(null),
|
||||
'ja_JP': () => new Future.value(null),
|
||||
'ml_IN': () => new Future.value(null),
|
||||
'nl_NL': () => new Future.value(null),
|
||||
'pl_PL': () => new Future.value(null),
|
||||
'pt_BR': () => new Future.value(null),
|
||||
|
|
@ -82,6 +84,8 @@ MessageLookupByLibrary? _findExact(String localeName) {
|
|||
return messages_it_it.messages;
|
||||
case 'ja_JP':
|
||||
return messages_ja_jp.messages;
|
||||
case 'ml_IN':
|
||||
return messages_ml_in.messages;
|
||||
case 'nl_NL':
|
||||
return messages_nl_nl.messages;
|
||||
case 'pl_PL':
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a ml_IN locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'ml_IN';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage("ബോൾഡ്"),
|
||||
"bulletedList":
|
||||
MessageLookupByLibrary.simpleMessage("ബുള്ളറ്റഡ് പട്ടിക"),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage("ചെക്ക്ബോക്സ്"),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage("എംബെഡഡ് കോഡ്"),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage("തലക്കെട്ട് 1"),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage("തലക്കെട്ട് 2"),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage("തലക്കെട്ട് 3"),
|
||||
"highlight":
|
||||
MessageLookupByLibrary.simpleMessage("പ്രമുഖമാക്കിക്കാട്ടുക"),
|
||||
"image": MessageLookupByLibrary.simpleMessage("ചിത്രം"),
|
||||
"italic": MessageLookupByLibrary.simpleMessage("ഇറ്റാലിക്"),
|
||||
"link": MessageLookupByLibrary.simpleMessage("ലിങ്ക്"),
|
||||
"numberedList":
|
||||
MessageLookupByLibrary.simpleMessage("അക്കമിട്ട പട്ടിക"),
|
||||
"quote": MessageLookupByLibrary.simpleMessage("ഉദ്ധരണി"),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage("സ്ട്രൈക്ക്ത്രൂ"),
|
||||
"text": MessageLookupByLibrary.simpleMessage("വചനം"),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("അടിവരയിടുക")
|
||||
};
|
||||
}
|
||||
|
|
@ -229,6 +229,7 @@ class AppLocalizationDelegate
|
|||
Locale.fromSubtags(languageCode: 'id', countryCode: 'ID'),
|
||||
Locale.fromSubtags(languageCode: 'it', countryCode: 'IT'),
|
||||
Locale.fromSubtags(languageCode: 'ja', countryCode: 'JP'),
|
||||
Locale.fromSubtags(languageCode: 'ml', countryCode: 'IN'),
|
||||
Locale.fromSubtags(languageCode: 'nl', countryCode: 'NL'),
|
||||
Locale.fromSubtags(languageCode: 'pl', countryCode: 'PL'),
|
||||
Locale.fromSubtags(languageCode: 'pt', countryCode: 'BR'),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy_editor/src/render/selection/selectable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CursorWidget extends StatefulWidget {
|
||||
|
|
@ -9,9 +10,13 @@ class CursorWidget extends StatefulWidget {
|
|||
required this.rect,
|
||||
required this.color,
|
||||
this.blinkingInterval = 0.5,
|
||||
this.shouldBlink = true,
|
||||
this.cursorStyle = CursorStyle.verticalLine,
|
||||
}) : super(key: key);
|
||||
|
||||
final double blinkingInterval; // milliseconds
|
||||
final bool shouldBlink;
|
||||
final CursorStyle cursorStyle;
|
||||
final Color color;
|
||||
final Rect rect;
|
||||
final LayerLink layerLink;
|
||||
|
|
@ -67,11 +72,28 @@ class CursorWidgetState extends State<CursorWidget> {
|
|||
// Ignore the gestures in cursor
|
||||
// to solve the problem that cursor area cannot be selected.
|
||||
child: IgnorePointer(
|
||||
child: Container(
|
||||
color: showCursor ? widget.color : Colors.transparent,
|
||||
),
|
||||
child: _buildCursor(context),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCursor(BuildContext context) {
|
||||
var color = widget.color;
|
||||
if (widget.shouldBlink && !showCursor) {
|
||||
color = Colors.transparent;
|
||||
}
|
||||
switch (widget.cursorStyle) {
|
||||
case CursorStyle.verticalLine:
|
||||
return Container(
|
||||
color: color,
|
||||
);
|
||||
case CursorStyle.borderLine:
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: color, width: 2),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@ import 'package:appflowy_editor/src/document/position.dart';
|
|||
import 'package:appflowy_editor/src/document/selection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum CursorStyle {
|
||||
verticalLine,
|
||||
borderLine,
|
||||
}
|
||||
|
||||
/// [SelectableMixin] is used for the editor to calculate the position
|
||||
/// and size of the selection.
|
||||
///
|
||||
|
|
@ -53,4 +58,8 @@ mixin SelectableMixin<T extends StatefulWidget> on State<T> {
|
|||
Selection? getWorldBoundaryInOffset(Offset start) {
|
||||
return null;
|
||||
}
|
||||
|
||||
bool get shouldCursorBlink => true;
|
||||
|
||||
CursorStyle get cursorStyle => CursorStyle.verticalLine;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ typedef SelectionMenuItemHandler = void Function(
|
|||
EditorState editorState,
|
||||
SelectionMenuService menuService,
|
||||
BuildContext context,
|
||||
);
|
||||
);
|
||||
|
||||
/// Selection Menu Item
|
||||
class SelectionMenuItem {
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ List<ToolbarItem> defaultToolbarItems = [
|
|||
highlightCallback: (editorState) => _allSatisfy(
|
||||
editorState,
|
||||
BuiltInAttributeKey.heading,
|
||||
<bool>(value) => value == BuiltInAttributeKey.h1,
|
||||
(value) => value == BuiltInAttributeKey.h1,
|
||||
),
|
||||
handler: (editorState, context) =>
|
||||
formatHeading(editorState, BuiltInAttributeKey.h1),
|
||||
|
|
@ -90,7 +90,7 @@ List<ToolbarItem> defaultToolbarItems = [
|
|||
highlightCallback: (editorState) => _allSatisfy(
|
||||
editorState,
|
||||
BuiltInAttributeKey.heading,
|
||||
<bool>(value) => value == BuiltInAttributeKey.h2,
|
||||
(value) => value == BuiltInAttributeKey.h2,
|
||||
),
|
||||
handler: (editorState, context) =>
|
||||
formatHeading(editorState, BuiltInAttributeKey.h2),
|
||||
|
|
@ -107,7 +107,7 @@ List<ToolbarItem> defaultToolbarItems = [
|
|||
highlightCallback: (editorState) => _allSatisfy(
|
||||
editorState,
|
||||
BuiltInAttributeKey.heading,
|
||||
<bool>(value) => value == BuiltInAttributeKey.h3,
|
||||
(value) => value == BuiltInAttributeKey.h3,
|
||||
),
|
||||
handler: (editorState, context) =>
|
||||
formatHeading(editorState, BuiltInAttributeKey.h3),
|
||||
|
|
@ -124,7 +124,7 @@ List<ToolbarItem> defaultToolbarItems = [
|
|||
highlightCallback: (editorState) => _allSatisfy(
|
||||
editorState,
|
||||
BuiltInAttributeKey.bold,
|
||||
<bool>(value) => value == true,
|
||||
(value) => value == true,
|
||||
),
|
||||
handler: (editorState, context) => formatBold(editorState),
|
||||
),
|
||||
|
|
@ -140,7 +140,7 @@ List<ToolbarItem> defaultToolbarItems = [
|
|||
highlightCallback: (editorState) => _allSatisfy(
|
||||
editorState,
|
||||
BuiltInAttributeKey.italic,
|
||||
<bool>(value) => value == true,
|
||||
(value) => value == true,
|
||||
),
|
||||
handler: (editorState, context) => formatItalic(editorState),
|
||||
),
|
||||
|
|
@ -156,7 +156,7 @@ List<ToolbarItem> defaultToolbarItems = [
|
|||
highlightCallback: (editorState) => _allSatisfy(
|
||||
editorState,
|
||||
BuiltInAttributeKey.underline,
|
||||
<bool>(value) => value == true,
|
||||
(value) => value == true,
|
||||
),
|
||||
handler: (editorState, context) => formatUnderline(editorState),
|
||||
),
|
||||
|
|
@ -172,7 +172,7 @@ List<ToolbarItem> defaultToolbarItems = [
|
|||
highlightCallback: (editorState) => _allSatisfy(
|
||||
editorState,
|
||||
BuiltInAttributeKey.strikethrough,
|
||||
<bool>(value) => value == true,
|
||||
(value) => value == true,
|
||||
),
|
||||
handler: (editorState, context) => formatStrikethrough(editorState),
|
||||
),
|
||||
|
|
@ -188,7 +188,7 @@ List<ToolbarItem> defaultToolbarItems = [
|
|||
highlightCallback: (editorState) => _allSatisfy(
|
||||
editorState,
|
||||
BuiltInAttributeKey.code,
|
||||
<bool>(value) => value == true,
|
||||
(value) => value == true,
|
||||
),
|
||||
handler: (editorState, context) => formatEmbedCode(editorState),
|
||||
),
|
||||
|
|
@ -204,7 +204,7 @@ List<ToolbarItem> defaultToolbarItems = [
|
|||
highlightCallback: (editorState) => _allSatisfy(
|
||||
editorState,
|
||||
BuiltInAttributeKey.subtype,
|
||||
<bool>(value) => value == BuiltInAttributeKey.quote,
|
||||
(value) => value == BuiltInAttributeKey.quote,
|
||||
),
|
||||
handler: (editorState, context) => formatQuote(editorState),
|
||||
),
|
||||
|
|
@ -220,7 +220,7 @@ List<ToolbarItem> defaultToolbarItems = [
|
|||
highlightCallback: (editorState) => _allSatisfy(
|
||||
editorState,
|
||||
BuiltInAttributeKey.subtype,
|
||||
<bool>(value) => value == BuiltInAttributeKey.bulletedList,
|
||||
(value) => value == BuiltInAttributeKey.bulletedList,
|
||||
),
|
||||
handler: (editorState, context) => formatBulletedList(editorState),
|
||||
),
|
||||
|
|
@ -236,7 +236,7 @@ List<ToolbarItem> defaultToolbarItems = [
|
|||
highlightCallback: (editorState) => _allSatisfy(
|
||||
editorState,
|
||||
BuiltInAttributeKey.href,
|
||||
<bool>(value) => value != null,
|
||||
(value) => value != null,
|
||||
),
|
||||
handler: (editorState, context) => showLinkMenu(context, editorState),
|
||||
),
|
||||
|
|
@ -252,7 +252,7 @@ List<ToolbarItem> defaultToolbarItems = [
|
|||
highlightCallback: (editorState) => _allSatisfy(
|
||||
editorState,
|
||||
BuiltInAttributeKey.backgroundColor,
|
||||
<bool>(value) => value != null,
|
||||
(value) => value != null,
|
||||
),
|
||||
handler: (editorState, context) => formatHighlight(
|
||||
editorState,
|
||||
|
|
@ -284,7 +284,7 @@ ToolbarItemValidator _showInBuiltInTextSelection = (editorState) {
|
|||
bool _allSatisfy(
|
||||
EditorState editorState,
|
||||
String styleKey,
|
||||
bool Function<T>(T value) test,
|
||||
bool Function(dynamic value) test,
|
||||
) {
|
||||
final selection = editorState.service.selectionService.currentSelection.value;
|
||||
return selection != null &&
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ bool _allSatisfyInSelection(
|
|||
return false;
|
||||
}
|
||||
|
||||
return textNodes.allSatisfyInSelection(selection, styleKey, <bool>(value) {
|
||||
return textNodes.allSatisfyInSelection(selection, styleKey, (value) {
|
||||
return value == matchValue;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,6 +83,11 @@ KeyEventResult _handleBackspace(EditorState editorState, RawKeyEvent event) {
|
|||
}
|
||||
} else {
|
||||
if (textNodes.isEmpty) {
|
||||
if (nonTextNodes.isNotEmpty) {
|
||||
transactionBuilder.afterSelection =
|
||||
Selection.collapsed(selection.start);
|
||||
}
|
||||
transactionBuilder.commit();
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
final startPosition = selection.start;
|
||||
|
|
|
|||
|
|
@ -457,6 +457,8 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
|
|||
rect: cursorRect,
|
||||
color: widget.cursorColor,
|
||||
layerLink: node.layerLink,
|
||||
shouldBlink: selectable.shouldCursorBlink,
|
||||
cursorStyle: selectable.cursorStyle,
|
||||
),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
name: appflowy_editor
|
||||
description: A highly customizable rich-text editor for Flutter
|
||||
version: 0.0.5
|
||||
version: 0.0.6
|
||||
homepage: https://github.com/AppFlowy-IO/AppFlowy
|
||||
|
||||
platforms:
|
||||
linux:
|
||||
macos:
|
||||
windows:
|
||||
web:
|
||||
|
||||
environment:
|
||||
sdk: ">=2.17.0 <3.0.0"
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue