parent
7ab6444520
commit
7282e721dd
1 changed files with 142 additions and 0 deletions
@ -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) |
Loading…
Reference in new issue