From f20678cb95e9f32328380e2bc1a6ddba607279f6 Mon Sep 17 00:00:00 2001 From: Karlo Verde <42245236+mrverdant13@users.noreply.github.com> Date: Thu, 6 Jan 2022 11:15:31 -0500 Subject: [PATCH] feat: support multiple links insertion on the go (#579) --- lib/src/models/rules/insert.dart | 105 +++++++++++++++++++++++++++++++ lib/src/models/rules/rule.dart | 1 + 2 files changed, 106 insertions(+) diff --git a/lib/src/models/rules/insert.dart b/lib/src/models/rules/insert.dart index 57b7117f..ab46e031 100644 --- a/lib/src/models/rules/insert.dart +++ b/lib/src/models/rules/insert.dart @@ -1,5 +1,6 @@ import 'package:tuple/tuple.dart'; +import '../../../models/documents/document.dart'; import '../documents/attribute.dart'; import '../documents/style.dart'; import '../quill_delta.dart'; @@ -286,6 +287,110 @@ class InsertEmbedsRule extends InsertRule { } } +/// 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 AutoFormatMultipleLinksRule extends InsertRule { + const AutoFormatMultipleLinksRule(); + + /// 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, + }) { + // 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); + + // Get unchanged text length. + final unmodifiedLength = index - leftWordPart.length; + + // Create formatter delta. + // The formatter delta will only include links formatting when needed. + final formatterDelta = Delta()..retain(unmodifiedLength); + + 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; + + // 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); + } +} + /// Applies link format to text segment (which looks like a link) when user /// inserts space character after it. class AutoFormatLinksRule extends InsertRule { diff --git a/lib/src/models/rules/rule.dart b/lib/src/models/rules/rule.dart index f01eb438..d29bc9a1 100644 --- a/lib/src/models/rules/rule.dart +++ b/lib/src/models/rules/rule.dart @@ -43,6 +43,7 @@ class Rules { const PreserveLineStyleOnSplitRule(), const ResetLineFormatOnNewLineRule(), const AutoFormatLinksRule(), + const AutoFormatMultipleLinksRule(), const PreserveInlineStylesRule(), const CatchAllInsertRule(), const EnsureEmbedLineRule(),