dartlangeditorflutterflutter-appsflutter-examplesflutter-packageflutter-widgetquillquill-deltaquilljsreactquillrich-textrich-text-editorwysiwygwysiwyg-editor
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
226 lines
5.6 KiB
226 lines
5.6 KiB
import 'package:meta/meta.dart' show immutable; |
|
|
|
import '../../../quill_delta.dart'; |
|
import '../documents/attribute.dart'; |
|
import 'rule.dart'; |
|
|
|
/// A heuristic rule for format (retain) operations. |
|
@immutable |
|
abstract class FormatRule extends Rule { |
|
const FormatRule(); |
|
|
|
@override |
|
RuleType get type => RuleType.format; |
|
|
|
@override |
|
void validateArgs(int? len, Object? data, Attribute? attribute) { |
|
assert(len != null); |
|
assert(data == null); |
|
assert(attribute != null); |
|
} |
|
} |
|
|
|
/// Produces Delta with line-level attributes applied strictly to |
|
/// newline characters. |
|
@immutable |
|
class ResolveLineFormatRule extends FormatRule { |
|
const ResolveLineFormatRule(); |
|
|
|
@override |
|
Delta? applyRule( |
|
Delta document, |
|
int index, { |
|
int? len, |
|
Object? data, |
|
Attribute? attribute, |
|
}) { |
|
if (attribute!.scope != AttributeScope.block) { |
|
return null; |
|
} |
|
|
|
// Apply line styles to all newline characters within range of this |
|
// retain operation. |
|
var result = Delta()..retain(index); |
|
final itr = DeltaIterator(document)..skip(index); |
|
Operation op; |
|
for (var cur = 0; cur < len! && itr.hasNext; cur += op.length!) { |
|
op = itr.next(len - cur); |
|
final opText = op.data is String ? op.data as String : ''; |
|
if (!opText.contains('\n')) { |
|
result.retain(op.length!); |
|
continue; |
|
} |
|
|
|
final delta = _applyAttribute(opText, op, attribute); |
|
result = result.concat(delta); |
|
} |
|
// And include extra newline after retain |
|
while (itr.hasNext) { |
|
op = itr.next(); |
|
final opText = op.data is String ? op.data as String : ''; |
|
final lf = opText.indexOf('\n'); |
|
if (lf < 0) { |
|
result.retain(op.length!); |
|
continue; |
|
} |
|
|
|
final delta = _applyAttribute(opText, op, attribute, firstOnly: true); |
|
result = result.concat(delta); |
|
break; |
|
} |
|
return result; |
|
} |
|
|
|
Delta _applyAttribute(String text, Operation op, Attribute attribute, |
|
{bool firstOnly = false}) { |
|
final result = Delta(); |
|
var offset = 0; |
|
var lf = text.indexOf('\n'); |
|
final removedBlocks = _getRemovedBlocks(attribute, op); |
|
while (lf >= 0) { |
|
final actualStyle = attribute.toJson()..addEntries(removedBlocks); |
|
result |
|
..retain(lf - offset) |
|
..retain(1, actualStyle); |
|
|
|
if (firstOnly) { |
|
return result; |
|
} |
|
|
|
offset = lf + 1; |
|
lf = text.indexOf('\n', offset); |
|
} |
|
// Retain any remaining characters in text |
|
result.retain(text.length - offset); |
|
return result; |
|
} |
|
|
|
Iterable<MapEntry<String, dynamic>> _getRemovedBlocks( |
|
Attribute<dynamic> attribute, Operation op) { |
|
// Enforce Block Format exclusivity by rule |
|
if (!Attribute.exclusiveBlockKeys.contains(attribute.key)) { |
|
return <MapEntry<String, dynamic>>[]; |
|
} |
|
|
|
return op.attributes?.keys |
|
.where((key) => |
|
Attribute.exclusiveBlockKeys.contains(key) && |
|
attribute.key != key && |
|
attribute.value != null) |
|
.map((key) => MapEntry<String, dynamic>(key, null)) ?? |
|
[]; |
|
} |
|
} |
|
|
|
/// Allows updating link format with collapsed selection. |
|
@immutable |
|
class FormatLinkAtCaretPositionRule extends FormatRule { |
|
const FormatLinkAtCaretPositionRule(); |
|
|
|
@override |
|
Delta? applyRule( |
|
Delta document, |
|
int index, { |
|
int? len, |
|
Object? data, |
|
Attribute? attribute, |
|
}) { |
|
if (attribute!.key != Attribute.link.key || len! > 0) { |
|
return null; |
|
} |
|
|
|
final delta = Delta(); |
|
final itr = DeltaIterator(document); |
|
final before = itr.skip(index), after = itr.next(); |
|
int? beg = index, retain = 0; |
|
if (before != null && before.hasAttribute(attribute.key)) { |
|
beg -= before.length!; |
|
retain = before.length; |
|
} |
|
if (after.hasAttribute(attribute.key)) { |
|
if (retain != null) retain += after.length!; |
|
} |
|
if (retain == 0) { |
|
return null; |
|
} |
|
|
|
delta |
|
..retain(beg) |
|
..retain(retain!, attribute.toJson()); |
|
return delta; |
|
} |
|
} |
|
|
|
/// Produces Delta with inline-level attributes applied to all characters |
|
/// except newlines. |
|
@immutable |
|
class ResolveInlineFormatRule extends FormatRule { |
|
const ResolveInlineFormatRule(); |
|
|
|
@override |
|
Delta? applyRule( |
|
Delta document, |
|
int index, { |
|
int? len, |
|
Object? data, |
|
Attribute? attribute, |
|
}) { |
|
if (attribute!.scope != AttributeScope.inline) { |
|
return null; |
|
} |
|
|
|
final delta = Delta()..retain(index); |
|
final itr = DeltaIterator(document)..skip(index); |
|
|
|
Operation op; |
|
for (var cur = 0; cur < len! && itr.hasNext; cur += op.length!) { |
|
op = itr.next(len - cur); |
|
final text = op.data is String ? (op.data as String?)! : ''; |
|
var lineBreak = text.indexOf('\n'); |
|
if (lineBreak < 0) { |
|
delta.retain(op.length!, attribute.toJson()); |
|
continue; |
|
} |
|
var pos = 0; |
|
while (lineBreak >= 0) { |
|
delta |
|
..retain(lineBreak - pos, attribute.toJson()) |
|
..retain(1); |
|
pos = lineBreak + 1; |
|
lineBreak = text.indexOf('\n', pos); |
|
} |
|
if (pos < op.length!) { |
|
delta.retain(op.length! - pos, attribute.toJson()); |
|
} |
|
} |
|
|
|
return delta; |
|
} |
|
} |
|
|
|
/// Produces Delta with attributes applied to image leaf node |
|
@immutable |
|
class ResolveImageFormatRule extends FormatRule { |
|
const ResolveImageFormatRule(); |
|
|
|
@override |
|
Delta? applyRule( |
|
Delta document, |
|
int index, { |
|
int? len, |
|
Object? data, |
|
Attribute? attribute, |
|
}) { |
|
if (attribute == null || attribute.key != Attribute.style.key) { |
|
return null; |
|
} |
|
|
|
assert(len == 1 && data == null); |
|
|
|
final delta = Delta() |
|
..retain(index) |
|
..retain(1, attribute.toJson()); |
|
|
|
return delta; |
|
} |
|
}
|
|
|