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.
207 lines
4.2 KiB
207 lines
4.2 KiB
import 'dart:math' as math; |
|
|
|
import 'package:quill_delta/quill_delta.dart'; |
|
|
|
import '../style.dart'; |
|
import 'embed.dart'; |
|
import 'line.dart'; |
|
import 'node.dart'; |
|
|
|
/* A leaf node in document tree */ |
|
abstract class Leaf extends Node { |
|
Object _value; |
|
|
|
Object get value => _value; |
|
|
|
Leaf.val(Object val) |
|
: assert(val != null), |
|
_value = val; |
|
|
|
factory Leaf([Object data]) { |
|
assert(data != null); |
|
|
|
if (data is Embeddable) { |
|
return Embed(data); |
|
} |
|
String text = data as String; |
|
assert(text.isNotEmpty); |
|
return Text(text); |
|
} |
|
|
|
@override |
|
void applyStyle(Style value) { |
|
assert(value != null && (value.isInline || value.isEmpty), |
|
'Unable to apply Style to leaf: $value'); |
|
super.applyStyle(value); |
|
} |
|
|
|
@override |
|
Line get parent => super.parent as Line; |
|
|
|
@override |
|
int get length { |
|
if (_value is String) { |
|
return (_value as String).length; |
|
} |
|
// return 1 for embedded object |
|
return 1; |
|
} |
|
|
|
@override |
|
Delta toDelta() { |
|
var data = _value is Embeddable ? (_value as Embeddable).toJson() : _value; |
|
return Delta()..insert(data, style.toJson()); |
|
} |
|
|
|
@override |
|
insert(int index, Object data, Style style) { |
|
assert(data != null && index >= 0 && index <= length); |
|
Leaf node = Leaf(data); |
|
if (index < length) { |
|
splitAt(index).insertBefore(node); |
|
} else { |
|
insertAfter(node); |
|
} |
|
node.format(style); |
|
} |
|
|
|
@override |
|
retain(int index, int len, Style style) { |
|
if (style == null) { |
|
return; |
|
} |
|
|
|
int local = math.min(this.length - index, len); |
|
int remain = len - local; |
|
Leaf node = _isolate(index, local); |
|
|
|
if (remain > 0) { |
|
assert(node.next != null); |
|
node.next.retain(0, remain, style); |
|
} |
|
node.format(style); |
|
} |
|
|
|
@override |
|
delete(int index, int len) { |
|
assert(index < this.length); |
|
|
|
int local = math.min(this.length - index, len); |
|
Leaf target = _isolate(index, local); |
|
Leaf prev = target.previous; |
|
Leaf next = target.next; |
|
target.unlink(); |
|
|
|
int remain = len - local; |
|
if (remain > 0) { |
|
assert(next != null); |
|
next.delete(0, remain); |
|
} |
|
|
|
if (prev != null) { |
|
prev.adjust(); |
|
} |
|
} |
|
|
|
@override |
|
adjust() { |
|
if (this is Embed) { |
|
return; |
|
} |
|
|
|
Text node = this as Text; |
|
// merging it with previous node if style is the same |
|
Node prev = node.previous; |
|
if (!node.isFirst && prev is Text && prev.style == node.style) { |
|
prev._value = prev.value + node.value; |
|
node.unlink(); |
|
node = prev; |
|
} |
|
|
|
// merging it with next node if style is the same |
|
Node next = node.next; |
|
if (!node.isLast && next is Text && next.style == node.style) { |
|
node._value = node.value + next.value; |
|
next.unlink(); |
|
} |
|
} |
|
|
|
Leaf cutAt(int index) { |
|
assert(index >= 0 && index <= length); |
|
Leaf cut = splitAt(index); |
|
cut?.unlink(); |
|
return cut; |
|
} |
|
|
|
Leaf splitAt(int index) { |
|
assert(index >= 0 && index <= length); |
|
if (index == 0) { |
|
return this; |
|
} |
|
if (index == length) { |
|
return isLast ? null : next as Leaf; |
|
} |
|
|
|
assert(this is Text); |
|
String text = _value as String; |
|
_value = text.substring(0, index); |
|
Leaf split = Leaf(text.substring(index)); |
|
split.applyStyle(style); |
|
insertAfter(split); |
|
return split; |
|
} |
|
|
|
format(Style style) { |
|
if (style != null && style.isNotEmpty) { |
|
applyStyle(style); |
|
} |
|
|
|
adjust(); |
|
} |
|
|
|
Leaf _isolate(int index, int length) { |
|
assert( |
|
index >= 0 && index < this.length && (index + length <= this.length)); |
|
Leaf target = splitAt(index); |
|
target.splitAt(length); |
|
return target; |
|
} |
|
} |
|
|
|
class Text extends Leaf { |
|
Text([String text = '']) |
|
: assert(!text.contains('\n')), |
|
super.val(text); |
|
|
|
@override |
|
String get value => _value as String; |
|
|
|
@override |
|
String toPlainText() { |
|
return value; |
|
} |
|
|
|
@override |
|
Node newInstance() { |
|
return Text(); |
|
} |
|
} |
|
|
|
/// An embedded node such as image or video |
|
class Embed extends Leaf { |
|
Embed(Embeddable data) : super.val(data); |
|
|
|
@override |
|
Embeddable get value => super.value as Embeddable; |
|
|
|
@override |
|
String toPlainText() { |
|
return '\uFFFC'; |
|
} |
|
|
|
@override |
|
Node newInstance() { |
|
throw UnimplementedError(); |
|
} |
|
|
|
}
|
|
|