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 '../documents/attribute.dart';
@ -286,44 +287,107 @@ class InsertEmbedsRule extends InsertRule {
}
}
/// Applies link format to text segment (which looks like a link) when user
/// inserts space character after it.
/// Applies link format to text segments within the inserted text that matches
/// the URL pattern.
///
/// The link attribute is applied as the user types.
class AutoFormatLinksRule extends InsertRule {
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
Delta? applyRule(Delta document, int index,
{int? len, Object? data, Attribute? attribute}) {
if (data is! String || data != ' ') {
return null;
}
Delta? applyRule(
Delta document,
int index, {
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);
final prev = itr.skip(index);
if (prev == null || prev.data is! String) {
return null;
}
// Get unchanged text length.
final unmodifiedLength = index - leftWordPart.length;
try {
final cand = (prev.data as String).split('\n').last.split(' ').last;
final link = Uri.parse(cand);
if (!['https', 'http'].contains(link.scheme)) {
return null;
}
final attributes = prev.attributes ?? <String, dynamic>{};
// Create formatter delta.
// The formatter delta will only include links formatting when needed.
final formatterDelta = Delta()..retain(unmodifiedLength);
if (attributes.containsKey(Attribute.link.key)) {
return null;
}
var previousLinkEndRelativeIndex = 0;
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());
return Delta()
..retain(index + (len ?? 0) - cand.length)
..retain(cand.length, attributes)
..insert(data, prev.attributes);
} on FormatException {
return null;
// Get the identified link.
final link = affectedWords.substring(match.start, match.end);
// Keep the leading segment of text and add link with its proper
// attribute.
formatterDelta
..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