From f816ad7ec84a2166c28d12b3a85acc88d0429e0e Mon Sep 17 00:00:00 2001 From: hyouuu Date: Mon, 3 May 2021 01:34:01 -0700 Subject: [PATCH] custom rules & optionally auto add newline for image embeds (#205) --- lib/models/documents/document.dart | 31 ++++++++++++++++++++---------- lib/models/rules/rule.dart | 8 +++++++- lib/widgets/controller.dart | 4 ++-- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/lib/models/documents/document.dart b/lib/models/documents/document.dart index 68dbee4d..9d966f32 100644 --- a/lib/models/documents/document.dart +++ b/lib/models/documents/document.dart @@ -40,6 +40,10 @@ class Document { final Rules _rules = Rules.getInstance(); + void setCustomRules(List customRules) { + _rules.setCustomRules(customRules); + } + final StreamController> _observer = StreamController.broadcast(); @@ -47,7 +51,7 @@ class Document { Stream> get changes => _observer.stream; - Delta insert(int index, Object? data, {int replaceLength = 0}) { + Delta insert(int index, Object? data, {int replaceLength = 0, bool autoAppendNewlineAfterImage = true}) { assert(index >= 0); assert(data is String || data is Embeddable); if (data is Embeddable) { @@ -58,7 +62,7 @@ class Document { final delta = _rules.apply(RuleType.INSERT, this, index, data: data, len: replaceLength); - compose(delta, ChangeSource.LOCAL); + compose(delta, ChangeSource.LOCAL, autoAppendNewlineAfterImage: autoAppendNewlineAfterImage); return delta; } @@ -71,7 +75,7 @@ class Document { return delta; } - Delta replace(int index, int len, Object? data) { + Delta replace(int index, int len, Object? data, {bool autoAppendNewlineAfterImage = true}) { assert(index >= 0); assert(data is String || data is Embeddable); @@ -84,7 +88,8 @@ class Document { // We have to insert before applying delete rules // Otherwise delete would be operating on stale document snapshot. if (dataIsNotEmpty) { - delta = insert(index, data, replaceLength: len); + delta = insert(index, data, replaceLength: len, + autoAppendNewlineAfterImage: autoAppendNewlineAfterImage); } if (len > 0) { @@ -124,13 +129,13 @@ class Document { return block.queryChild(res.offset, true); } - void compose(Delta delta, ChangeSource changeSource) { + void compose(Delta delta, ChangeSource changeSource, {bool autoAppendNewlineAfterImage = true}) { assert(!_observer.isClosed); delta.trim(); assert(delta.isNotEmpty); var offset = 0; - delta = _transform(delta); + delta = _transform(delta, autoAppendNewlineAfterImage: autoAppendNewlineAfterImage); final originalDelta = toDelta(); for (final op in delta.toList()) { final style = @@ -174,22 +179,28 @@ class Document { bool get hasRedo => _history.hasRedo; - static Delta _transform(Delta delta) { + static Delta _transform(Delta delta, {bool autoAppendNewlineAfterImage = true}) { final res = Delta(); final ops = delta.toList(); for (var i = 0; i < ops.length; i++) { final op = ops[i]; res.push(op); - _handleImageInsert(i, ops, op, res); + if (autoAppendNewlineAfterImage) { + _autoAppendNewlineAfterImage(i, ops, op, res); + } } return res; } - static void _handleImageInsert( + static void _autoAppendNewlineAfterImage( int i, List ops, Operation op, Delta res) { final nextOpIsImage = i + 1 < ops.length && ops[i + 1].isInsert && ops[i + 1].data is! String; - if (nextOpIsImage && !(op.data as String).endsWith('\n')) { + if (nextOpIsImage && + op.data is String && + (op.data as String).isNotEmpty && + !(op.data as String).endsWith('\n')) + { res.push(Operation.insert('\n')); } // Currently embed is equivalent to image and hence `is! String` diff --git a/lib/models/rules/rule.dart b/lib/models/rules/rule.dart index 4ee6c278..042f1aaa 100644 --- a/lib/models/rules/rule.dart +++ b/lib/models/rules/rule.dart @@ -28,6 +28,8 @@ abstract class Rule { class Rules { Rules(this._rules); + List _customRules = []; + final List _rules; static final Rules _instance = Rules([ const FormatLinkAtCaretPositionRule(), @@ -49,10 +51,14 @@ class Rules { static Rules getInstance() => _instance; + void setCustomRules(List customRules) { + _customRules = customRules; + } + Delta apply(RuleType ruleType, Document document, int index, {int? len, Object? data, Attribute? attribute}) { final delta = document.toDelta(); - for (final rule in _rules) { + for (final rule in _customRules + _rules) { if (rule.type != ruleType) { continue; } diff --git a/lib/widgets/controller.dart b/lib/widgets/controller.dart index cb5136df..4820b3f9 100644 --- a/lib/widgets/controller.dart +++ b/lib/widgets/controller.dart @@ -92,12 +92,12 @@ class QuillController extends ChangeNotifier { void replaceText( int index, int len, Object? data, TextSelection? textSelection, - {bool ignoreFocus = false}) { + {bool ignoreFocus = false, bool autoAppendNewlineAfterImage = true}) { assert(data is String || data is Embeddable); Delta? delta; if (len > 0 || data is! String || data.isNotEmpty) { - delta = document.replace(index, len, data); + delta = document.replace(index, len, data, autoAppendNewlineAfterImage: autoAppendNewlineAfterImage); var shouldRetainDelta = toggledStyle.isNotEmpty && delta.isNotEmpty && delta.length <= 2 &&