diff --git a/frontend/app_flowy/packages/appflowy_editor/assets/images/toolbar/highlight.svg b/frontend/app_flowy/packages/appflowy_editor/assets/images/toolbar/highlight.svg
new file mode 100644
index 0000000000..697603a054
--- /dev/null
+++ b/frontend/app_flowy/packages/appflowy_editor/assets/images/toolbar/highlight.svg
@@ -0,0 +1,5 @@
+
diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/extensions/text_node_extensions.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/extensions/text_node_extensions.dart
index 3b62edf598..1d7c68ab80 100644
--- a/frontend/app_flowy/packages/appflowy_editor/lib/src/extensions/text_node_extensions.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/extensions/text_node_extensions.dart
@@ -7,18 +7,22 @@ import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
extension TextNodeExtension on TextNode {
bool allSatisfyBoldInSelection(Selection selection) =>
- allSatisfyInSelection(StyleKey.bold, selection);
+ allSatisfyInSelection(StyleKey.bold, true, selection);
bool allSatisfyItalicInSelection(Selection selection) =>
- allSatisfyInSelection(StyleKey.italic, selection);
+ allSatisfyInSelection(StyleKey.italic, true, selection);
bool allSatisfyUnderlineInSelection(Selection selection) =>
- allSatisfyInSelection(StyleKey.underline, selection);
+ allSatisfyInSelection(StyleKey.underline, true, selection);
bool allSatisfyStrikethroughInSelection(Selection selection) =>
- allSatisfyInSelection(StyleKey.strikethrough, selection);
+ allSatisfyInSelection(StyleKey.strikethrough, true, selection);
- bool allSatisfyInSelection(String styleKey, Selection selection) {
+ bool allSatisfyInSelection(
+ String styleKey,
+ dynamic value,
+ Selection selection,
+ ) {
final ops = delta.whereType();
final startOffset =
selection.isBackward ? selection.start.offset : selection.end.offset;
@@ -33,7 +37,7 @@ extension TextNodeExtension on TextNode {
if (start < endOffset && start + length > startOffset) {
if (op.attributes == null ||
!op.attributes!.containsKey(styleKey) ||
- op.attributes![styleKey] == false) {
+ op.attributes![styleKey] != value) {
return false;
}
}
@@ -42,7 +46,11 @@ extension TextNodeExtension on TextNode {
return true;
}
- bool allNotSatisfyInSelection(String styleKey, Selection selection) {
+ bool allNotSatisfyInSelection(
+ String styleKey,
+ dynamic value,
+ Selection selection,
+ ) {
final ops = delta.whereType();
final startOffset =
selection.isBackward ? selection.start.offset : selection.end.offset;
@@ -57,7 +65,7 @@ extension TextNodeExtension on TextNode {
if (start < endOffset && start + length > startOffset) {
if (op.attributes != null &&
op.attributes!.containsKey(styleKey) &&
- op.attributes![styleKey] == true) {
+ op.attributes![styleKey] == value) {
return false;
}
}
@@ -69,23 +77,27 @@ extension TextNodeExtension on TextNode {
extension TextNodesExtension on List {
bool allSatisfyBoldInSelection(Selection selection) =>
- allSatisfyInSelection(StyleKey.bold, selection);
+ allSatisfyInSelection(StyleKey.bold, selection, true);
bool allSatisfyItalicInSelection(Selection selection) =>
- allSatisfyInSelection(StyleKey.italic, selection);
+ allSatisfyInSelection(StyleKey.italic, selection, true);
bool allSatisfyUnderlineInSelection(Selection selection) =>
- allSatisfyInSelection(StyleKey.underline, selection);
+ allSatisfyInSelection(StyleKey.underline, selection, true);
bool allSatisfyStrikethroughInSelection(Selection selection) =>
- allSatisfyInSelection(StyleKey.strikethrough, selection);
+ allSatisfyInSelection(StyleKey.strikethrough, selection, true);
- bool allSatisfyInSelection(String styleKey, Selection selection) {
+ bool allSatisfyInSelection(
+ String styleKey,
+ Selection selection,
+ dynamic value,
+ ) {
if (isEmpty) {
return false;
}
if (length == 1) {
- return first.allSatisfyInSelection(styleKey, selection);
+ return first.allSatisfyInSelection(styleKey, value, selection);
} else {
for (var i = 0; i < length; i++) {
final node = this[i];
@@ -105,7 +117,7 @@ extension TextNodesExtension on List {
end: Position(path: node.path, offset: node.toRawString().length),
);
}
- if (!node.allSatisfyInSelection(styleKey, newSelection)) {
+ if (!node.allSatisfyInSelection(styleKey, value, newSelection)) {
return false;
}
}
diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/rich_text_style.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/rich_text_style.dart
index 36489893f9..7bd68c45e7 100644
--- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/rich_text_style.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/rich_text_style.dart
@@ -66,6 +66,8 @@ class StyleKey {
double defaultMaxTextNodeWidth = 780.0;
double defaultLinePadding = 8.0;
double baseFontSize = 16.0;
+String defaultHighlightColor = '0x6000BCF0';
+String defaultBackgroundColor = '0x00000000';
// TODO: customize.
Map headingToFontSize = {
StyleKey.h1: baseFontSize + 15,
diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection/toolbar_widget.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection/toolbar_widget.dart
index ab2a6d884b..2f4eae8f93 100644
--- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection/toolbar_widget.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection/toolbar_widget.dart
@@ -16,6 +16,7 @@ ToolbarEventHandlers defaultToolbarEventHandlers = {
'underline': (editorState) => formatUnderline(editorState),
'quote': (editorState) => formatQuote(editorState),
'bulleted_list': (editorState) => formatBulletedList(editorState),
+ 'highlight': (editorState) => formatHighlight(editorState),
'Text': (editorState) => formatText(editorState),
'H1': (editorState) => formatHeading(editorState, StyleKey.h1),
'H2': (editorState) => formatHeading(editorState, StyleKey.h2),
@@ -103,6 +104,8 @@ class _ToolbarWidgetState extends State with ToolbarMixin {
_centerToolbarIcon('quote'),
// _centerToolbarIcon('number_list'),
_centerToolbarIcon('bulleted_list'),
+ _centerToolbarIcon('divider', width: 2),
+ _centerToolbarIcon('highlight'),
],
),
),
diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/default_text_operations/format_rich_text_style.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/default_text_operations/format_rich_text_style.dart
index 5576e11d3e..c4f765f2f4 100644
--- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/default_text_operations/format_rich_text_style.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/default_text_operations/format_rich_text_style.dart
@@ -139,7 +139,25 @@ bool formatStrikethrough(EditorState editorState) {
return formatRichTextPartialStyle(editorState, StyleKey.strikethrough);
}
-bool formatRichTextPartialStyle(EditorState editorState, String styleKey) {
+bool formatHighlight(EditorState editorState) {
+ bool value = _allSatisfyInSelection(
+ editorState, StyleKey.backgroundColor, defaultHighlightColor);
+ return formatRichTextPartialStyle(editorState, StyleKey.backgroundColor,
+ customValue: value ? defaultBackgroundColor : defaultHighlightColor);
+}
+
+bool formatRichTextPartialStyle(EditorState editorState, String styleKey,
+ {Object? customValue}) {
+ Attributes attributes = {
+ styleKey: customValue ??
+ !_allSatisfyInSelection(editorState, styleKey, customValue ?? true),
+ };
+
+ return formatRichTextStyle(editorState, attributes);
+}
+
+bool _allSatisfyInSelection(
+ EditorState editorState, String styleKey, dynamic value) {
final selection = editorState.service.selectionService.currentSelection.value;
final nodes = editorState.service.selectionService.currentSelectedNodes;
final textNodes = nodes.whereType().toList(growable: false);
@@ -148,12 +166,7 @@ bool formatRichTextPartialStyle(EditorState editorState, String styleKey) {
return false;
}
- bool value = !textNodes.allSatisfyInSelection(styleKey, selection);
- Attributes attributes = {
- styleKey: value,
- };
-
- return formatRichTextStyle(editorState, attributes);
+ return textNodes.allSatisfyInSelection(styleKey, selection, value);
}
bool formatRichTextStyle(EditorState editorState, Attributes attributes) {
diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/update_text_style_by_command_x_handler.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/update_text_style_by_command_x_handler.dart
index 2d22819799..e5ecb12e8d 100644
--- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/update_text_style_by_command_x_handler.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/update_text_style_by_command_x_handler.dart
@@ -31,6 +31,10 @@ FlowyKeyEventHandler updateTextStyleByCommandXHandler = (editorState, event) {
event.isShiftPressed) {
formatStrikethrough(editorState);
return KeyEventResult.handled;
+ } else if (event.logicalKey == LogicalKeyboardKey.keyH &&
+ event.isShiftPressed) {
+ formatHighlight(editorState);
+ return KeyEventResult.handled;
}
return KeyEventResult.ignored;
diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/selection_service.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/selection_service.dart
index 5470e5b091..b55c5e8de1 100644
--- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/selection_service.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/selection_service.dart
@@ -80,7 +80,7 @@ class FlowySelection extends StatefulWidget {
const FlowySelection({
Key? key,
this.cursorColor = Colors.black,
- this.selectionColor = const Color.fromARGB(60, 61, 61, 213),
+ this.selectionColor = const Color.fromARGB(53, 111, 201, 231),
required this.editorState,
required this.child,
}) : super(key: key);
diff --git a/frontend/app_flowy/packages/appflowy_editor/test/infra/test_raw_key_event.dart b/frontend/app_flowy/packages/appflowy_editor/test/infra/test_raw_key_event.dart
index 04b5a11789..f54528064c 100644
--- a/frontend/app_flowy/packages/appflowy_editor/test/infra/test_raw_key_event.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/test/infra/test_raw_key_event.dart
@@ -115,6 +115,9 @@ extension on LogicalKeyboardKey {
if (this == LogicalKeyboardKey.keyU) {
return PhysicalKeyboardKey.keyU;
}
+ if (this == LogicalKeyboardKey.keyH) {
+ return PhysicalKeyboardKey.keyH;
+ }
if (this == LogicalKeyboardKey.keyZ) {
return PhysicalKeyboardKey.keyZ;
}
diff --git a/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/update_text_style_by_command_x_handler_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/update_text_style_by_command_x_handler_test.dart
index 31a881fe45..2e93d4c5f5 100644
--- a/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/update_text_style_by_command_x_handler_test.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/update_text_style_by_command_x_handler_test.dart
@@ -15,6 +15,7 @@ void main() async {
await _testUpdateTextStyleByCommandX(
tester,
StyleKey.bold,
+ true,
LogicalKeyboardKey.keyB,
);
});
@@ -22,6 +23,7 @@ void main() async {
await _testUpdateTextStyleByCommandX(
tester,
StyleKey.italic,
+ true,
LogicalKeyboardKey.keyI,
);
});
@@ -29,21 +31,40 @@ void main() async {
await _testUpdateTextStyleByCommandX(
tester,
StyleKey.underline,
+ true,
LogicalKeyboardKey.keyU,
);
});
- testWidgets('Presses Command + S to update text style', (tester) async {
+ testWidgets('Presses Command + Shift + S to update text style',
+ (tester) async {
await _testUpdateTextStyleByCommandX(
tester,
StyleKey.strikethrough,
+ true,
LogicalKeyboardKey.keyS,
);
});
+
+ testWidgets('Presses Command + Shift + H to update text style',
+ (tester) async {
+ await _testUpdateTextStyleByCommandX(
+ tester,
+ StyleKey.backgroundColor,
+ defaultHighlightColor,
+ LogicalKeyboardKey.keyH,
+ );
+ });
});
}
Future _testUpdateTextStyleByCommandX(
- WidgetTester tester, String matchStyle, LogicalKeyboardKey key) async {
+ WidgetTester tester,
+ String matchStyle,
+ dynamic matchValue,
+ LogicalKeyboardKey key,
+) async {
+ final isShiftPressed =
+ key == LogicalKeyboardKey.keyS || key == LogicalKeyboardKey.keyH;
const text = 'Welcome to Appflowy 😁';
final editor = tester.editor
..insertTextNode(text)
@@ -56,31 +77,34 @@ Future _testUpdateTextStyleByCommandX(
await editor.updateSelection(selection);
await editor.pressLogicKey(
key,
- isShiftPressed: key == LogicalKeyboardKey.keyS,
+ isShiftPressed: isShiftPressed,
isMetaPressed: true,
);
var textNode = editor.nodeAtPath([1]) as TextNode;
- expect(textNode.allSatisfyInSelection(matchStyle, selection), true);
+ expect(
+ textNode.allSatisfyInSelection(matchStyle, matchValue, selection), true);
selection =
Selection.single(path: [1], startOffset: 0, endOffset: text.length);
await editor.updateSelection(selection);
await editor.pressLogicKey(
key,
- isShiftPressed: key == LogicalKeyboardKey.keyS,
+ isShiftPressed: isShiftPressed,
isMetaPressed: true,
);
textNode = editor.nodeAtPath([1]) as TextNode;
- expect(textNode.allSatisfyInSelection(matchStyle, selection), true);
+ expect(
+ textNode.allSatisfyInSelection(matchStyle, matchValue, selection), true);
await editor.updateSelection(selection);
await editor.pressLogicKey(
key,
- isShiftPressed: key == LogicalKeyboardKey.keyS,
+ isShiftPressed: isShiftPressed,
isMetaPressed: true,
);
textNode = editor.nodeAtPath([1]) as TextNode;
- expect(textNode.allNotSatisfyInSelection(matchStyle, selection), true);
+ expect(textNode.allNotSatisfyInSelection(matchStyle, matchValue, selection),
+ true);
selection = Selection(
start: Position(path: [0], offset: 0),
@@ -89,7 +113,7 @@ Future _testUpdateTextStyleByCommandX(
await editor.updateSelection(selection);
await editor.pressLogicKey(
key,
- isShiftPressed: key == LogicalKeyboardKey.keyS,
+ isShiftPressed: isShiftPressed,
isMetaPressed: true,
);
var nodes = editor.editorState.service.selectionService.currentSelectedNodes
@@ -99,6 +123,7 @@ Future _testUpdateTextStyleByCommandX(
expect(
node.allSatisfyInSelection(
matchStyle,
+ matchValue,
Selection.single(
path: node.path, startOffset: 0, endOffset: text.length),
),
@@ -109,7 +134,7 @@ Future _testUpdateTextStyleByCommandX(
await editor.updateSelection(selection);
await editor.pressLogicKey(
key,
- isShiftPressed: key == LogicalKeyboardKey.keyS,
+ isShiftPressed: isShiftPressed,
isMetaPressed: true,
);
nodes = editor.editorState.service.selectionService.currentSelectedNodes
@@ -119,6 +144,7 @@ Future _testUpdateTextStyleByCommandX(
expect(
node.allNotSatisfyInSelection(
matchStyle,
+ matchValue,
Selection.single(
path: node.path, startOffset: 0, endOffset: text.length),
),