Skip to content

Commit

Permalink
Feat: customizable character and space shortcut events (#2228)
Browse files Browse the repository at this point in the history
* Added character shortcut events class

* Feat: support for character and space  shortcut events

* Chore: added new gif video url

* Chore: improve and fix some parts of the new section in the README.md

* Merge from master

* Fix: typo in comment into the Shortcut event section

* chore: add a message for validating an assert in handleFormatByWrappingWithSingleCharacter()

* chore: fix analysis warning

* Chore: moved shortcut docs to its own file

* Chore: changed editor without shortcut gif url

* Chore: added description to asserts

* Chore: removed unnecessary default cases in format functions

* Fix: characterShortcutEvents param in raw configs is showing as it is deprecated

* Chore: dart format

* Chore: fixed some comments that references double chars instead single chars

* Fix: typo

* Chore: improved doc comments on format functions

* Chore: dart format

---------

Co-authored-by: CatHood0 <[email protected]>
Co-authored-by: Ellet <[email protected]>
  • Loading branch information
3 people authored Sep 15, 2024
1 parent 164c183 commit 289139b
Show file tree
Hide file tree
Showing 19 changed files with 807 additions and 46 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,13 @@ You can join our [Slack Group] for discussion.
- [📦 Embed Blocks](#-embed-blocks)
- [🔄 Conversion to HTML](#-conversion-to-html)
- [📝 Spelling checker](#-spelling-checker)
- [✂️ Shortcut events](#-shortcut-events)
- [🌐 Translation](#-translation)
- [🧪 Testing](#-testing)
- [🤝 Contributing](#-contributing)
- [📜 Acknowledgments](#-acknowledgments)


## 📸 Screenshots

<details>
Expand Down Expand Up @@ -290,6 +292,16 @@ It's implemented using the package `simple_spell_checker` in the [Example](./exa

Take a look at [Spelling Checker](./doc/spell_checker.md) page for more info.

## ✂️ Shortcut events

We can customize some Shorcut events, using the parameters `characterShortcutEvents` or `spaceShortcutEvents` from `QuillEditorConfigurations` to add more functionality to our editor.

> [!NOTE]
>
> You can get all standard shortcuts using `standardCharactersShortcutEvents` or `standardSpaceShorcutEvents`

To see an example of this, you can check [customizing_shortcuts](./doc/customizing_shortcuts.md)

## 🌐 Translation

The package offers translations for the quill toolbar and editor, it will follow the system locale unless you set your
Expand Down
101 changes: 101 additions & 0 deletions doc/customizing_shortcuts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Shortcut events

We will use a simple example to illustrate how to quickly add a `CharacterShortcutEvent` event.

In this example, text that starts and ends with an asterisk ( * ) character will be rendered in italics for emphasis. So typing `*xxx*` will automatically be converted into _`xxx`_.

Let's start with a empty document:

```dart
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter/material.dart';
class AsteriskToItalicStyle extends StatelessWidget {
const AsteriskToItalicStyle({super.key});
@override
Widget build(BuildContext context) {
return QuillEditor(
scrollController: <your_scrollController>,
focusNode: <your_focusNode>,
controller: <your_controller>,
configurations: QuillEditorConfigurations(
characterShortcutEvents: [],
),
);
}
}
```

At this point, nothing magic will happen after typing `*xxx*`.

<p align="center">
<img src="https://github.com/user-attachments/assets/c9ab15ec-2ada-4a84-96e8-55e6145e7925" width="800px" alt="Editor without shortcuts gif">
</p>

To implement our shortcut event we will create a `CharacterShortcutEvent` instance to handle an asterisk input.

We need to define key and character in a `CharacterShortcutEvent` object to customize hotkeys. We recommend using the description of your event as a key. For example, if the asterisk `*` is defined to make text italic, the key can be 'Asterisk to italic'.

```dart
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter/material.dart';
// [handleFormatByWrappingWithSingleCharacter] is a example function that contains
// the necessary logic to replace asterisk characters and apply correctly the
// style to the text around them
enum SingleCharacterFormatStyle {
code,
italic,
strikethrough,
}
CharacterShortcutEvent asteriskToItalicStyleEvent = CharacterShortcutEvent(
key: 'Asterisk to italic',
character: '*',
handler: (QuillController controller) => handleFormatByWrappingWithSingleCharacter(
controller: controller,
character: '*',
formatStyle: SingleCharacterFormatStyle.italic,
),
);
```

Now our 'asterisk handler' function is done and the only task left is to inject it into the `QuillEditorConfigurations`.

```dart
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter/material.dart';
class AsteriskToItalicStyle extends StatelessWidget {
const AsteriskToItalicStyle({super.key});
@override
Widget build(BuildContext context) {
return QuillEditor(
scrollController: <your_scrollController>,
focusNode: <your_focusNode>,
controller: <your_controller>,
configurations: QuillEditorConfigurations(
characterShortcutEvents: [
asteriskToItalicStyleEvent,
],
),
);
}
}
CharacterShortcutEvent asteriskToItalicStyleEvent = CharacterShortcutEvent(
key: 'Asterisk to italic',
character: '*',
handler: (QuillController controller) => handleFormatByWrappingWithSingleCharacter(
controller: controller,
character: '*',
formatStyle: SingleCharacterFormatStyle.italic,
),
);
```
<p align="center">
<img src="https://github.com/user-attachments/assets/35e74cbf-1bd8-462d-bb90-50d712012c90" width="800px" alt="Editor with shortcuts gif">
</p>
2 changes: 2 additions & 0 deletions example/lib/screens/quill/quill_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ class _QuillScreenState extends State<QuillScreen> {
child: MyQuillEditor(
controller: _controller,
configurations: QuillEditorConfigurations(
characterShortcutEvents: standardCharactersShortcutEvents,
spaceShortcutEvents: standardSpaceShorcutEvents,
searchConfigurations: const QuillSearchConfigurations(
searchEmbedMode: SearchEmbedMode.plainText,
),
Expand Down
1 change: 1 addition & 0 deletions lib/flutter_quill.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export 'src/editor/editor.dart';
export 'src/editor/embed/embed_editor_builder.dart';
export 'src/editor/provider.dart';
export 'src/editor/raw_editor/builders/leading_block_builder.dart';
export 'src/editor/raw_editor/config/events/events.dart';
export 'src/editor/raw_editor/config/raw_editor_configurations.dart';
export 'src/editor/raw_editor/quill_single_child_scroll_view.dart';
export 'src/editor/raw_editor/raw_editor.dart';
Expand Down
62 changes: 62 additions & 0 deletions lib/src/editor/config/editor_configurations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import '../../toolbar/theme/quill_dialog_theme.dart';
import '../editor_builder.dart';
import '../embed/embed_editor_builder.dart';
import '../raw_editor/builders/leading_block_builder.dart';
import '../raw_editor/config/events/events.dart';
import '../raw_editor/raw_editor.dart';
import '../widgets/default_styles.dart';
import '../widgets/delegate.dart';
Expand All @@ -33,6 +34,8 @@ class QuillEditorConfigurations extends Equatable {
this.sharedConfigurations = const QuillSharedConfigurations(),
this.scrollable = true,
this.padding = EdgeInsets.zero,
this.characterShortcutEvents = const [],
this.spaceShortcutEvents = const [],
this.autoFocus = false,
this.expands = false,
this.placeholder,
Expand All @@ -57,6 +60,8 @@ class QuillEditorConfigurations extends Equatable {
this.onSingleLongTapStart,
this.onSingleLongTapMoveUpdate,
this.onSingleLongTapEnd,
@Deprecated(
'Use space/char shortcut events instead - enableMarkdownStyleConversion will be removed in future releases.')
this.enableMarkdownStyleConversion = true,
this.enableAlwaysIndentOnTab = false,
this.embedBuilders,
Expand Down Expand Up @@ -102,6 +107,52 @@ class QuillEditorConfigurations extends Equatable {
/// The text placeholder in the quill editor
final String? placeholder;

/// Contains all the events that will be handled when
/// the exact characters satifies the condition. This mean
/// if you press asterisk key, if you have a `CharacterShortcutEvent` with
/// the asterisk then that event will be handled
///
/// Supported by:
///
/// - Web
/// - Desktop
/// ### Example
///```dart
/// // you can get also the default implemented shortcuts
/// // calling [standardSpaceShorcutEvents]
///final defaultShorcutsImplementation =
/// List.from([...standardCharactersShortcutEvents])
///
///final boldFormat = CharacterShortcutEvent(
/// key: 'Shortcut event that will format current wrapped text in asterisk'
/// character: '*',
/// handler: (controller) {...your implementation}
///);
///```
final List<CharacterShortcutEvent> characterShortcutEvents;

/// Contains all the events that will be handled when
/// space key is pressed
///
/// Supported by:
///
/// - Web
/// - Desktop
///
/// ### Example
///```dart
/// // you can get also the default implemented shortcuts
/// // calling [standardSpaceShorcutEvents]
///final defaultShorcutsImplementation =
/// List.from([...standardSpaceShorcutEvents])
///
///final spaceBulletList = SpaceShortcutEvent(
/// character: '-',
/// handler: (QuillText textNode, controller) {...your implementation}
///);
///```
final List<SpaceShortcutEvent> spaceShortcutEvents;

/// Whether the text can be changed.
///
/// When this is set to `true`, the text cannot be modified
Expand Down Expand Up @@ -145,6 +196,10 @@ class QuillEditorConfigurations extends Equatable {
/// This setting controls the behavior of input. Specifically, when enabled,
/// entering '1.' followed by a space or '-' followed by a space
/// will automatically convert the input into a Markdown list format.
///
/// ## !This functionality now does not work because was replaced by a more advanced using [SpaceShortcutEvent] and [CharacterShortcutEvent] classes
@Deprecated(
'enableMarkdownStyleConversion is no longer used and will be removed in future releases. Use space/char shortcut events instead.')
final bool enableMarkdownStyleConversion;

/// Enables always indenting when the TAB key is pressed.
Expand Down Expand Up @@ -450,6 +505,8 @@ class QuillEditorConfigurations extends Equatable {
LinkActionPickerDelegate? linkActionPickerDelegate,
bool? floatingCursorDisabled,
TextSelectionControls? textSelectionControls,
List<CharacterShortcutEvent>? characterShortcutEvents,
List<SpaceShortcutEvent>? spaceShortcutEvents,
Future<String?> Function(Uint8List imageBytes)? onImagePaste,
Future<String?> Function(Uint8List imageBytes)? onGifPaste,
Map<ShortcutActivator, Intent>? customShortcuts,
Expand Down Expand Up @@ -483,8 +540,13 @@ class QuillEditorConfigurations extends Equatable {
disableClipboard: disableClipboard ?? this.disableClipboard,
scrollable: scrollable ?? this.scrollable,
scrollBottomInset: scrollBottomInset ?? this.scrollBottomInset,
characterShortcutEvents:
characterShortcutEvents ?? this.characterShortcutEvents,
spaceShortcutEvents: spaceShortcutEvents ?? this.spaceShortcutEvents,
padding: padding ?? this.padding,
// ignore: deprecated_member_use_from_same_package
enableMarkdownStyleConversion:
// ignore: deprecated_member_use_from_same_package
enableMarkdownStyleConversion ?? this.enableMarkdownStyleConversion,
enableAlwaysIndentOnTab:
enableAlwaysIndentOnTab ?? this.enableAlwaysIndentOnTab,
Expand Down
5 changes: 3 additions & 2 deletions lib/src/editor/editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -295,13 +295,14 @@ class QuillEditorState extends State<QuillEditor>
key: _editorKey,
controller: controller,
configurations: QuillRawEditorConfigurations(
characterShortcutEvents:
widget.configurations.characterShortcutEvents,
spaceShortcutEvents: widget.configurations.spaceShortcutEvents,
customLeadingBuilder:
widget.configurations.customLeadingBlockBuilder,
focusNode: widget.focusNode,
scrollController: widget.scrollController,
scrollable: configurations.scrollable,
enableMarkdownStyleConversion:
configurations.enableMarkdownStyleConversion,
enableAlwaysIndentOnTab: configurations.enableAlwaysIndentOnTab,
scrollBottomInset: configurations.scrollBottomInset,
padding: configurations.padding,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';

import '../../../../../flutter_quill.dart';

typedef CharacterShortcutEventHandler = bool Function(
QuillController controller);

/// Defines the implementation of shortcut event based on character.
@immutable
class CharacterShortcutEvent extends Equatable {
const CharacterShortcutEvent({
required this.key,
required this.character,
required this.handler,
}) : assert(character.length == 1 && character != '\n',
'character cannot be major than one char, and it must not be a new line');

final String key;
final String character;
final CharacterShortcutEventHandler handler;

bool execute(QuillController controller) {
return handler(controller);
}

CharacterShortcutEvent copyWith({
String? key,
String? character,
CharacterShortcutEventHandler? handler,
}) {
return CharacterShortcutEvent(
key: key ?? this.key,
character: character ?? this.character,
handler: handler ?? this.handler,
);
}

@override
String toString() =>
'CharacterShortcutEvent(key: $key, character: $character, handler: $handler)';

@override
List<Object?> get props => [key, character, handler];
}
9 changes: 9 additions & 0 deletions lib/src/editor/raw_editor/config/events/events.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// event classes
export 'character_shortcuts_events.dart';
export 'space_shortcut_events.dart';
// default implementation of the shortcuts
export 'standard_char_shortcuts/block_shortcut_events_handlers.dart';
export 'standard_char_shortcuts/double_character_shortcut_events.dart';
export 'standard_char_shortcuts/single_character_shortcut_events.dart';
// all available shortcuts
export 'standard_char_shortcuts/standard_shortcut_events.dart';
Loading

0 comments on commit 289139b

Please sign in to comment.