Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

doc: more documentation about Rules #2014

Merged
merged 1 commit into from
Jul 14, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions doc/rules_introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
## Rule

`Rule` in `flutter_quill` is a handler for specific operations within the editor. They define how to apply, modify, or delete content based on user actions. Each Rule corresponds to a type of operation that the editor can perform.

### RuleType

There are three main `RuleTypes` supported in `flutter_quill`, each serving a distinct purpose:

- **insert**: Handles operations related to inserting new content into the editor. This includes inserting text, images, or any other supported media.

- **delete**: Manages operations that involve deleting content from the editor. This can include deleting characters, lines, or entire blocks of content.

- **format**: Deals with operations that apply formatting changes to the content in the editor. This could involve applying styles such as bold, italic, underline, or changing text alignment, among others.

### How Rules Work

When a user interacts with the editor in `flutter_quill`, their actions are translated into one of the predefined `RuleType`. For instance:

- When the user types a new character, an **insert** Rule is triggered to handle the insertion of that character into the editor's content.
- When the user selects and deletes a block of text, a **delete** Rule is used to remove that selection from the editor.
- Applying formatting, such as making text bold or italic, triggers a **format** Rule to update the style of the selected text.

`Rule` is designed to be modular and configurable, allowing developers to extend or customize editor behavior as needed. By defining how each RuleType operates, `flutter_quill` ensures consistent and predictable behavior across different editing operations.


### Example of a custom `Rule`

In this case, we will use a simple example. We will create a `Rule` that is responsible for detecting any word that is surrounded by "*" just as any `Markdown` editor would do for italics.

In order for it to be detected while the user writes character by character, what we will do is extend the `InsertRule` class that is responsible for being called while the user writes a word character by character.

```dart
/// Applies italic format to text segment (which is surrounded by *)
/// when user inserts space character after it.
class AutoFormatItalicRule extends InsertRule {
const AutoFormatItalicRule();

static const _italicPattern = r'\*(.+)\*';

RegExp get italicRegExp => RegExp(
_italicPattern,
caseSensitive: false,
);

@override
Delta? applyRule(
Document document,
int index, {
int? len,
Object? data,
Attribute? attribute,
Object? extraData,
}) {
// Only format when inserting text.
if (data is! String) return null;

// Get current text.
final entireText = document.toPlainText();

// Get word before insertion.
final leftWordPart = entireText
// Keep all text before insertion.
.substring(0, index)
// Keep last paragraph.
.split('\n')
.last
// Keep last word.
.split(' ')
.last
.trimLeft();

// Get word after insertion.
final rightWordPart = entireText
// Keep all text after insertion.
.substring(index)
// Keep first paragraph.
.split('\n')
.first
// Keep first word.
.split(' ')
.first
.trimRight();

// Build the segment of affected words.
final affectedWords = '$leftWordPart$data$rightWordPart';

// Check for italic patterns.
final italicMatches = italicRegExp.allMatches(affectedWords);

// If there are no matches, do not apply any format.
if (italicMatches.isEmpty) return null;

// Build base delta.
// The base delta is a simple insertion delta.
final baseDelta = Delta()
..retain(index)
..insert(data);

// Get unchanged text length.
final unmodifiedLength = index - leftWordPart.length;

// Create formatter delta.
// The formatter delta will include italic formatting when needed.
final formatterDelta = Delta()..retain(unmodifiedLength);

var previousEndRelativeIndex = 0;

void retainWithAttributes(int start, int end, Map<String, dynamic> attributes) {
final separationLength = start - previousEndRelativeIndex;
final segment = affectedWords.substring(start, end);
formatterDelta
..retain(separationLength)
..retain(segment.length, attributes);
previousEndRelativeIndex = end;
}

for (final match in italicMatches) {
final matchStart = match.start;
final matchEnd = match.end;

retainWithAttributes(matchStart + 1, matchEnd - 1, const ItalicAttribute().toJson());
}

// Get remaining text length.
final remainingLength = affectedWords.length - previousEndRelativeIndex;

// Remove italic from remaining non-italic text.
formatterDelta.retain(remainingLength);

// Build resulting change delta.
return baseDelta.compose(formatterDelta);
}
}
```

To apply any custom `Rule` you can use `setCustomRules` that is exposed on `Document`

```dart
quillController.document.setCustomRules([const AutoFormatItalicRule()]);
```

You can see a example video [here](https://e.pcloud.link/publink/show?code=XZ2NzgZrb888sWjuxFjzWoBpe7HlLymKp3V)