feat: support multiple links insertion on the go

pull/579/head
Karlo Verde 3 years ago
parent 430c6ae46e
commit 810ab17ee2
  1. 122
      lib/src/models/rules/insert.dart

@ -1,3 +1,4 @@
import 'package:flutter_quill/flutter_quill.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import '../documents/attribute.dart'; import '../documents/attribute.dart';
@ -286,44 +287,107 @@ class InsertEmbedsRule extends InsertRule {
} }
} }
/// Applies link format to text segment (which looks like a link) when user /// Applies link format to text segments within the inserted text that matches
/// inserts space character after it. /// the URL pattern.
///
/// The link attribute is applied as the user types.
class AutoFormatLinksRule extends InsertRule { class AutoFormatLinksRule extends InsertRule {
const AutoFormatLinksRule(); const AutoFormatLinksRule();
/// Link pattern.
///
/// This pattern is used to match a links within a text segment.
static const _linkPattern =
r'(https?:\/\/|www\.)[\w-\.]+\.[\w-\.]+(\/([\S]+)?)?';
static final _linkRegExp = RegExp(_linkPattern);
@override @override
Delta? applyRule(Delta document, int index, Delta? applyRule(
{int? len, Object? data, Attribute? attribute}) { Delta document,
if (data is! String || data != ' ') { int index, {
return null; int? len,
} Object? data,
Attribute? attribute,
}) {
// Only format when inserting text.
if (data is! String) return null;
// Get current text.
final entireText = Document.fromDelta(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 URL pattern.
final matches = _linkRegExp.allMatches(affectedWords);
// If there are no matches, do not apply any format.
if (matches.isEmpty) return null;
// Build base delta.
// The base delta is a simple insertion delta.
final baseDelta = Delta()
..retain(index)
..insert(data);
final itr = DeltaIterator(document); // Get unchanged text length.
final prev = itr.skip(index); final unmodifiedLength = index - leftWordPart.length;
if (prev == null || prev.data is! String) {
return null;
}
try { // Create formatter delta.
final cand = (prev.data as String).split('\n').last.split(' ').last; // The formatter delta will only include links formatting when needed.
final link = Uri.parse(cand); final formatterDelta = Delta()..retain(unmodifiedLength);
if (!['https', 'http'].contains(link.scheme)) {
return null;
}
final attributes = prev.attributes ?? <String, dynamic>{};
if (attributes.containsKey(Attribute.link.key)) { var previousLinkEndRelativeIndex = 0;
return null; for (final match in matches) {
} // Get the size of the leading segment of text that is not part of the
// link.
final separationLength = match.start - previousLinkEndRelativeIndex;
attributes.addAll(LinkAttribute(link.toString()).toJson()); // Get the identified link.
return Delta() final link = affectedWords.substring(match.start, match.end);
..retain(index + (len ?? 0) - cand.length)
..retain(cand.length, attributes) // Keep the leading segment of text and add link with its proper
..insert(data, prev.attributes); // attribute.
} on FormatException { formatterDelta
return null; ..retain(separationLength, LinkAttribute(null).toJson())
..retain(link.length, LinkAttribute(link).toJson());
// Update reference index.
previousLinkEndRelativeIndex = match.end;
} }
// Get remaining text length.
final remainingLength = affectedWords.length - previousLinkEndRelativeIndex;
// Remove links from remaining non-link text.
formatterDelta.retain(remainingLength, LinkAttribute(null).toJson());
// Build and return resulting change delta.
return baseDelta.compose(formatterDelta);
} }
} }

Loading…
Cancel
Save