Skip to content

Commit

Permalink
feat: find dialog 1760 (#106)
Browse files Browse the repository at this point in the history
* feat: make find menu widget

* feat: service for find menu

* feat: add find menu shortcut event

* feat: create a search service

* docs: explain search service

* fix: unhighlight method takes searched word

* fix: unhighlight before each search

* feat: navigate between matches

* feat: forget highlighting from undo stack

* feat: replace logic and ui

* feat: replace shortcut handler and widget

* test: find functionality

* test: replace menu tests

* refactor: separate class for search algo

* refactor: suggested changes

* refactor: remove unhighlight method

* feat: add find highlight color

* refactor: name of the search algo class

* test: unit tests for search algorithm

* chore: simplify syntax

* test: renamed test group

* refactor: move to editor

* refactor: add shortcut for find

* refactor: use new api

* refactor: add abstrac class search algo

* fix: avoid multiple instances of find dialog

* refactor: xazin's suggestions

* chore: separately build input decor

* test: search algorithm

* chore: unhighlight properly

* refactor: replace handler

* refactor: move tests into new

* test: find menu widget test

* test: replace menu

* fix: localizations + resolve scroll bug partially

* fix: without update selection on highlight

* fix: do not select words when highlighting

* test: update expected selection

* fix: matches and styling

* feat: separate attribute for highlighting

* feat: unique color for selected match

* test: unique color for selected match

* refactor: cleaning the code

* fix: navigate to first match

* fix: tests and replace logic

---------

Co-authored-by: Mathias Mogensen <[email protected]>
Co-authored-by: Mathias Mogensen <[email protected]>
  • Loading branch information
3 people authored Aug 10, 2023
1 parent 953249f commit f5c6dc2
Show file tree
Hide file tree
Showing 26 changed files with 1,630 additions and 25 deletions.
15 changes: 14 additions & 1 deletion example/lib/pages/editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,20 @@ class Editor extends StatelessWidget {
editorState: editorState,
scrollController: scrollController,
blockComponentBuilders: customBlockComponentBuilders,
commandShortcutEvents: standardCommandShortcutEvents,
commandShortcutEvents: [
...standardCommandShortcutEvents,
...findAndReplaceCommands(
context: context,
localizations: FindReplaceLocalizations(
find: 'Find',
previousMatch: 'Previous match',
nextMatch: 'Next match',
close: 'Close',
replace: 'Replace',
replaceAll: 'Replace all',
),
),
],
characterShortcutEvents: standardCharacterShortcutEvents,
);
}
Expand Down
7 changes: 7 additions & 0 deletions example/windows/runner/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ add_executable(${BINARY_NAME} WIN32
# that need different build settings.
apply_standard_settings(${BINARY_NAME})

# Add preprocessor definitions for the build version.
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}")

# Disable Windows macros that collide with C++ standard library functions.
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")

Expand Down
10 changes: 5 additions & 5 deletions example/windows/runner/Runner.rc
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico"
// Version
//

#ifdef FLUTTER_BUILD_NUMBER
#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
#else
#define VERSION_AS_NUMBER 1,0,0
#define VERSION_AS_NUMBER 1,0,0,0
#endif

#ifdef FLUTTER_BUILD_NAME
#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
#if defined(FLUTTER_VERSION)
#define VERSION_AS_STRING FLUTTER_VERSION
#else
#define VERSION_AS_STRING "1.0.0"
#endif
Expand Down
11 changes: 11 additions & 0 deletions lib/src/editor/block_component/rich_text/appflowy_rich_text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,11 @@ class _AppFlowyRichTextState extends State<AppFlowyRichText>
TextStyle(backgroundColor: attributes.backgroundColor),
);
}
if (attributes.findBackgroundColor != null) {
textStyle = textStyle.combine(
TextStyle(backgroundColor: attributes.findBackgroundColor),
);
}
if (attributes.color != null) {
textStyle = textStyle.combine(
TextStyle(color: attributes.color),
Expand Down Expand Up @@ -407,6 +412,12 @@ extension AppFlowyRichTextAttributes on Attributes {
return highlightColor?.toColor();
}

Color? get findBackgroundColor {
final findBackgroundColor =
this[AppFlowyRichTextKeys.findBackgroundColor] as String?;
return findBackgroundColor?.toColor();
}

String? get href {
if (this[AppFlowyRichTextKeys.href] is String) {
return this[AppFlowyRichTextKeys.href];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class AppFlowyRichTextKeys {
static String strikethrough = 'strikethrough';
static String textColor = 'font_color';
static String highlightColor = 'bg_color';
static String findBackgroundColor = 'find_bg_color';
static String code = 'code';
static String href = 'href';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ final List<CommandShortcutEvent> standardCommandShortcutEvents = [
indentCommand,
outdentCommand,

//
exitEditingCommand,

//
Expand Down
11 changes: 9 additions & 2 deletions lib/src/editor/command/text_commands.dart
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,11 @@ extension TextTransforms on EditorState {
/// format the delta at the given selection.
///
/// If the [Selection] is not passed in, use the current selection.
Future<void> formatDelta(Selection? selection, Attributes attributes) async {
Future<void> formatDelta(
Selection? selection,
Attributes attributes, [
bool withUpdateSelection = true,
]) async {
selection ??= this.selection;
selection = selection?.normalized;

Expand Down Expand Up @@ -164,7 +168,10 @@ extension TextTransforms on EditorState {
..afterSelection = transaction.beforeSelection;
}

return apply(transaction);
return apply(
transaction,
withUpdateSelection: withUpdateSelection,
);
}

/// Toggles the given attribute on or off for the selected text.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,17 +104,28 @@ class _ScrollServiceWidgetState extends State<ScrollServiceWidget>
// should auto scroll after the cursor or selection updated.
final selection = editorState.selection;
if (selection == null ||
editorState.selectionUpdateReason == SelectionUpdateReason.selectAll) {
[SelectionUpdateReason.selectAll, SelectionUpdateReason.searchHighlight]
.contains(editorState.selectionUpdateReason)) {
return;
}

final updateReason = editorState.selectionUpdateReason;
final selectionType = editorState.selectionType;

WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
final selectionRect = editorState.selectionRects();
if (selectionRect.isEmpty) {
return;
}

final endTouchPoint = selectionRect.last.centerRight;

if (editorState.selectionUpdateReason ==
SelectionUpdateReason.searchNavigate) {
scrollController.jumpTo(endTouchPoint.dy - 100);
return;
}

if (selection.isCollapsed) {
if (PlatformExtension.isMobile) {
// soft keyboard
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,11 @@ class _DesktopSelectionServiceWidgetState

void _updateSelection() {
final selection = editorState.selection;

// TODO: why do we need to check this?
if (currentSelection.value == selection &&
editorState.selectionUpdateReason == SelectionUpdateReason.uiEvent &&
[SelectionUpdateReason.uiEvent, SelectionUpdateReason.searchHighlight]
.contains(editorState.selectionUpdateReason) &&
editorState.selectionType != SelectionType.block) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export 'page_down_command.dart';
export 'page_up_command.dart';
export 'paste_command.dart';
export 'remove_word_command.dart';
export 'find_replace_command.dart';
export 'select_all_command.dart';
export 'show_link_menu_command.dart';
export 'undo_redo_command.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/editor/find_replace_menu/find_menu_service.dart';
import 'package:flutter/material.dart';

List<CommandShortcutEvent> findAndReplaceCommands({
required FindReplaceLocalizations localizations,
required BuildContext context,
FindReplaceStyle? style,
}) =>
[
openFindDialog(
localizations: localizations,
context: context,
style: style ?? FindReplaceStyle(),
),
openReplaceDialog(
localizations: localizations,
context: context,
style: style ?? FindReplaceStyle(),
),
];

class FindReplaceStyle {
FindReplaceStyle({
this.selectedHighlightColor = const Color(0xFFFFB931),
this.unselectedHighlightColor = const Color(0x60ECBC5F),
});

//selected highlight color is used as background color on the selected found pattern.
final Color selectedHighlightColor;
//unselected highlight color is used on every other found pattern which can be selected.
final Color unselectedHighlightColor;
}

class FindReplaceLocalizations {
FindReplaceLocalizations({
required this.find,
required this.previousMatch,
required this.nextMatch,
required this.close,
required this.replace,
required this.replaceAll,
});

final String find;
final String previousMatch;
final String nextMatch;
final String close;
final String replace;
final String replaceAll;
}

/// Show the slash menu
///
/// - support
/// - desktop
/// - web
///
CommandShortcutEvent openFindDialog({
required FindReplaceLocalizations localizations,
required BuildContext context,
required FindReplaceStyle style,
}) =>
CommandShortcutEvent(
key: 'show the find dialog',
command: 'ctrl+f',
macOSCommand: 'cmd+f',
handler: (editorState) => _showFindAndReplaceDialog(
context,
editorState,
localizations: localizations,
style: style,
),
);

CommandShortcutEvent openReplaceDialog({
required FindReplaceLocalizations localizations,
required BuildContext context,
required FindReplaceStyle style,
}) =>
CommandShortcutEvent(
key: 'show the find and replace dialog',
command: 'ctrl+h',
macOSCommand: 'cmd+h',
handler: (editorState) => _showFindAndReplaceDialog(
context,
editorState,
localizations: localizations,
style: style,
openReplace: true,
),
);

FindReplaceService? _findReplaceService;
KeyEventResult _showFindAndReplaceDialog(
BuildContext context,
EditorState editorState, {
required FindReplaceLocalizations localizations,
required FindReplaceStyle style,
bool openReplace = false,
}) {
if (PlatformExtension.isMobile) {
return KeyEventResult.ignored;
}

_findReplaceService = FindReplaceMenu(
context: context,
editorState: editorState,
replaceFlag: openReplace,
localizations: localizations,
style: style,
);

_findReplaceService?.show();

return KeyEventResult.handled;
}
Loading

0 comments on commit f5c6dc2

Please sign in to comment.