From 7282e721dd63e4e0850142cb386b974d6158d48b Mon Sep 17 00:00:00 2001 From: Cat <114286961+CatHood0@users.noreply.github.com> Date: Sun, 14 Jul 2024 17:15:27 -0400 Subject: [PATCH] doc(rule): added documentation about rules (#2014) --- doc/rules_introduction.md | 142 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 doc/rules_introduction.md diff --git a/doc/rules_introduction.md b/doc/rules_introduction.md new file mode 100644 index 00000000..b0472120 --- /dev/null +++ b/doc/rules_introduction.md @@ -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 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)