parent
fd13ad47da
commit
cf5e83d370
60 changed files with 1875 additions and 1198 deletions
|
|
@ -2,3 +2,13 @@ include: package:flutter_lints/flutter.yaml
|
|||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
linter:
|
||||
rules:
|
||||
- always_declare_return_types
|
||||
- require_trailing_commas
|
||||
- unnecessary_raw_strings
|
||||
- use_decorated_box
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- lib/src/l10n/**
|
||||
|
|
|
|||
|
|
@ -52,7 +52,8 @@ extension TextCommands on EditorState {
|
|||
if (BuiltInAttributeKey.globalStyleKeys.contains(key)) {
|
||||
final attr = n.attributes
|
||||
..removeWhere(
|
||||
(key, _) => BuiltInAttributeKey.globalStyleKeys.contains(key))
|
||||
(key, _) => BuiltInAttributeKey.globalStyleKeys.contains(key),
|
||||
)
|
||||
..addAll(attributes)
|
||||
..addAll({
|
||||
BuiltInAttributeKey.subtype: key,
|
||||
|
|
|
|||
|
|
@ -247,7 +247,9 @@ class Delta extends Iterable<TextOperation> {
|
|||
@override
|
||||
int get length {
|
||||
return _operations.fold(
|
||||
0, (previousValue, element) => previousValue + element.length);
|
||||
0,
|
||||
(previousValue, element) => previousValue + element.length,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns a Delta that is equivalent to applying the operations of own Delta, followed by another Delta.
|
||||
|
|
|
|||
|
|
@ -58,11 +58,13 @@ class Transaction {
|
|||
/// The [attributes] will be merged into the existing attributes.
|
||||
void updateNode(Node node, Attributes attributes) {
|
||||
final inverted = invertAttributes(node.attributes, attributes);
|
||||
add(UpdateOperation(
|
||||
add(
|
||||
UpdateOperation(
|
||||
node.path,
|
||||
{...attributes},
|
||||
inverted,
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes the [Node] in the document.
|
||||
|
|
@ -170,10 +172,12 @@ extension TextTransaction on Transaction {
|
|||
..delete(firstLength - firstOffset)
|
||||
..addAll(second.delta.slice(secondOffset, secondLength)),
|
||||
);
|
||||
afterSelection = Selection.collapsed(Position(
|
||||
afterSelection = Selection.collapsed(
|
||||
Position(
|
||||
path: first.path,
|
||||
offset: firstOffset,
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void splitText(TextNode textNode, int offset) {
|
||||
|
|
@ -188,10 +192,12 @@ extension TextTransaction on Transaction {
|
|||
delta: second,
|
||||
),
|
||||
);
|
||||
afterSelection = Selection.collapsed(Position(
|
||||
afterSelection = Selection.collapsed(
|
||||
Position(
|
||||
path: path,
|
||||
offset: 0,
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts the text content at a specified index.
|
||||
|
|
|
|||
|
|
@ -80,7 +80,8 @@ extension DeltaAttributesExtensions on Attributes {
|
|||
if (containsKey(BuiltInAttributeKey.backgroundColor) &&
|
||||
this[BuiltInAttributeKey.backgroundColor] is String) {
|
||||
return Color(
|
||||
int.tryParse(this[BuiltInAttributeKey.backgroundColor]) ?? whiteInt);
|
||||
int.tryParse(this[BuiltInAttributeKey.backgroundColor]) ?? whiteInt,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -300,16 +300,20 @@ class Overlay extends StatefulWidget {
|
|||
final List<DiagnosticsNode> information = <DiagnosticsNode>[
|
||||
ErrorSummary('No Overlay widget found.'),
|
||||
ErrorDescription(
|
||||
'${debugRequiredFor.runtimeType} widgets require an Overlay widget ancestor for correct operation.'),
|
||||
'${debugRequiredFor.runtimeType} widgets require an Overlay widget ancestor for correct operation.',
|
||||
),
|
||||
ErrorHint(
|
||||
'The most common way to add an Overlay to an application is to include a MaterialApp or Navigator widget in the runApp() call.'),
|
||||
'The most common way to add an Overlay to an application is to include a MaterialApp or Navigator widget in the runApp() call.',
|
||||
),
|
||||
DiagnosticsProperty<Widget>(
|
||||
'The specific widget that failed to find an overlay was',
|
||||
debugRequiredFor,
|
||||
style: DiagnosticsTreeStyle.errorProperty),
|
||||
style: DiagnosticsTreeStyle.errorProperty,
|
||||
),
|
||||
if (context.widget != debugRequiredFor)
|
||||
context.describeElement(
|
||||
'The context from which that widget was searching for an overlay was'),
|
||||
'The context from which that widget was searching for an overlay was',
|
||||
),
|
||||
];
|
||||
|
||||
throw FlutterError.fromParts(information);
|
||||
|
|
@ -352,10 +356,14 @@ class OverlayState extends State<Overlay> with TickerProviderStateMixin {
|
|||
/// It is an error to specify both `above` and `below`.
|
||||
void insert(OverlayEntry entry, {OverlayEntry? below, OverlayEntry? above}) {
|
||||
assert(_debugVerifyInsertPosition(above, below));
|
||||
assert(!_entries.contains(entry),
|
||||
'The specified entry is already present in the Overlay.');
|
||||
assert(entry._overlay == null,
|
||||
'The specified entry is already present in another Overlay.');
|
||||
assert(
|
||||
!_entries.contains(entry),
|
||||
'The specified entry is already present in the Overlay.',
|
||||
);
|
||||
assert(
|
||||
entry._overlay == null,
|
||||
'The specified entry is already present in another Overlay.',
|
||||
);
|
||||
entry._overlay = this;
|
||||
setState(() {
|
||||
_entries.insert(_insertionIndex(below, above), entry);
|
||||
|
|
@ -369,8 +377,11 @@ class OverlayState extends State<Overlay> with TickerProviderStateMixin {
|
|||
/// Otherwise, the entries are inserted on top.
|
||||
///
|
||||
/// It is an error to specify both `above` and `below`.
|
||||
void insertAll(Iterable<OverlayEntry> entries,
|
||||
{OverlayEntry? below, OverlayEntry? above}) {
|
||||
void insertAll(
|
||||
Iterable<OverlayEntry> entries, {
|
||||
OverlayEntry? below,
|
||||
OverlayEntry? above,
|
||||
}) {
|
||||
assert(_debugVerifyInsertPosition(above, below));
|
||||
assert(
|
||||
entries.every((OverlayEntry entry) => !_entries.contains(entry)),
|
||||
|
|
@ -390,8 +401,11 @@ class OverlayState extends State<Overlay> with TickerProviderStateMixin {
|
|||
});
|
||||
}
|
||||
|
||||
bool _debugVerifyInsertPosition(OverlayEntry? above, OverlayEntry? below,
|
||||
{Iterable<OverlayEntry>? newEntries}) {
|
||||
bool _debugVerifyInsertPosition(
|
||||
OverlayEntry? above,
|
||||
OverlayEntry? below, {
|
||||
Iterable<OverlayEntry>? newEntries,
|
||||
}) {
|
||||
assert(
|
||||
above == null || below == null,
|
||||
'Only one of `above` and `below` may be specified.',
|
||||
|
|
@ -430,21 +444,29 @@ class OverlayState extends State<Overlay> with TickerProviderStateMixin {
|
|||
/// below.
|
||||
///
|
||||
/// It is an error to specify both `above` and `below`.
|
||||
void rearrange(Iterable<OverlayEntry> newEntries,
|
||||
{OverlayEntry? below, OverlayEntry? above}) {
|
||||
void rearrange(
|
||||
Iterable<OverlayEntry> newEntries, {
|
||||
OverlayEntry? below,
|
||||
OverlayEntry? above,
|
||||
}) {
|
||||
final List<OverlayEntry> newEntriesList = newEntries is List<OverlayEntry>
|
||||
? newEntries
|
||||
: newEntries.toList(growable: false);
|
||||
assert(
|
||||
_debugVerifyInsertPosition(above, below, newEntries: newEntriesList));
|
||||
_debugVerifyInsertPosition(above, below, newEntries: newEntriesList),
|
||||
);
|
||||
assert(
|
||||
newEntriesList.every((OverlayEntry entry) =>
|
||||
entry._overlay == null || entry._overlay == this),
|
||||
newEntriesList.every(
|
||||
(OverlayEntry entry) =>
|
||||
entry._overlay == null || entry._overlay == this,
|
||||
),
|
||||
'One or more of the specified entries are already present in another Overlay.',
|
||||
);
|
||||
assert(
|
||||
newEntriesList.every((OverlayEntry entry) =>
|
||||
_entries.indexOf(entry) == _entries.lastIndexOf(entry)),
|
||||
newEntriesList.every(
|
||||
(OverlayEntry entry) =>
|
||||
_entries.indexOf(entry) == _entries.lastIndexOf(entry),
|
||||
),
|
||||
'One or more of the specified entries are specified multiple times.',
|
||||
);
|
||||
if (newEntriesList.isEmpty) return;
|
||||
|
|
@ -509,17 +531,21 @@ class OverlayState extends State<Overlay> with TickerProviderStateMixin {
|
|||
final OverlayEntry entry = _entries[i];
|
||||
if (onstage) {
|
||||
onstageCount += 1;
|
||||
children.add(_OverlayEntryWidget(
|
||||
children.add(
|
||||
_OverlayEntryWidget(
|
||||
key: entry._key,
|
||||
entry: entry,
|
||||
));
|
||||
),
|
||||
);
|
||||
if (entry.opaque) onstage = false;
|
||||
} else if (entry.maintainState) {
|
||||
children.add(_OverlayEntryWidget(
|
||||
children.add(
|
||||
_OverlayEntryWidget(
|
||||
key: entry._key,
|
||||
entry: entry,
|
||||
tickerEnabled: false,
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return _Theatre(
|
||||
|
|
@ -684,26 +710,34 @@ class _RenderTheatre extends RenderBox
|
|||
|
||||
@override
|
||||
double computeMinIntrinsicWidth(double height) {
|
||||
return RenderStack.getIntrinsicDimension(_firstOnstageChild,
|
||||
(RenderBox child) => child.getMinIntrinsicWidth(height));
|
||||
return RenderStack.getIntrinsicDimension(
|
||||
_firstOnstageChild,
|
||||
(RenderBox child) => child.getMinIntrinsicWidth(height),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
double computeMaxIntrinsicWidth(double height) {
|
||||
return RenderStack.getIntrinsicDimension(_firstOnstageChild,
|
||||
(RenderBox child) => child.getMaxIntrinsicWidth(height));
|
||||
return RenderStack.getIntrinsicDimension(
|
||||
_firstOnstageChild,
|
||||
(RenderBox child) => child.getMaxIntrinsicWidth(height),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
double computeMinIntrinsicHeight(double width) {
|
||||
return RenderStack.getIntrinsicDimension(_firstOnstageChild,
|
||||
(RenderBox child) => child.getMinIntrinsicHeight(width));
|
||||
return RenderStack.getIntrinsicDimension(
|
||||
_firstOnstageChild,
|
||||
(RenderBox child) => child.getMinIntrinsicHeight(width),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
double computeMaxIntrinsicHeight(double width) {
|
||||
return RenderStack.getIntrinsicDimension(_firstOnstageChild,
|
||||
(RenderBox child) => child.getMaxIntrinsicHeight(width));
|
||||
return RenderStack.getIntrinsicDimension(
|
||||
_firstOnstageChild,
|
||||
(RenderBox child) => child.getMaxIntrinsicHeight(width),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -764,7 +798,11 @@ class _RenderTheatre extends RenderBox
|
|||
_resolvedAlignment!.alongOffset(size - child.size as Offset);
|
||||
} else {
|
||||
_hasVisualOverflow = RenderStack.layoutPositionedChild(
|
||||
child, childParentData, size, _resolvedAlignment!) ||
|
||||
child,
|
||||
childParentData,
|
||||
size,
|
||||
_resolvedAlignment!,
|
||||
) ||
|
||||
_hasVisualOverflow;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,17 +22,17 @@ class HistoryItem extends LinkedListEntry<HistoryItem> {
|
|||
/// to the item.
|
||||
///
|
||||
/// The caller should create a new [HistoryItem].
|
||||
seal() {
|
||||
void seal() {
|
||||
_sealed = true;
|
||||
}
|
||||
|
||||
bool get sealed => _sealed;
|
||||
|
||||
add(Operation op) {
|
||||
void add(Operation op) {
|
||||
operations.add(op);
|
||||
}
|
||||
|
||||
addAll(Iterable<Operation> iterable) {
|
||||
void addAll(Iterable<Operation> iterable) {
|
||||
operations.addAll(iterable);
|
||||
}
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ class FixedSizeStack {
|
|||
|
||||
FixedSizeStack(this.maxSize);
|
||||
|
||||
push(HistoryItem stackItem) {
|
||||
void push(HistoryItem stackItem) {
|
||||
if (_list.length >= maxSize) {
|
||||
_list.remove(_list.first);
|
||||
}
|
||||
|
|
@ -74,7 +74,7 @@ class FixedSizeStack {
|
|||
return last;
|
||||
}
|
||||
|
||||
clear() {
|
||||
void clear() {
|
||||
_list.clear();
|
||||
}
|
||||
|
||||
|
|
@ -110,7 +110,7 @@ class UndoManager {
|
|||
return last;
|
||||
}
|
||||
|
||||
undo() {
|
||||
void undo() {
|
||||
Log.editor.debug('undo');
|
||||
final s = state;
|
||||
if (s == null) {
|
||||
|
|
@ -130,7 +130,7 @@ class UndoManager {
|
|||
);
|
||||
}
|
||||
|
||||
redo() {
|
||||
void redo() {
|
||||
Log.editor.debug('redo');
|
||||
final s = state;
|
||||
if (s == null) {
|
||||
|
|
|
|||
|
|
@ -103,7 +103,8 @@ class HTMLToNodesConverter {
|
|||
for (final child in element.nodes.toList()) {
|
||||
if (child is html.Element) {
|
||||
result.addAll(
|
||||
_handleElement(child, {"subtype": BuiltInAttributeKey.quote}));
|
||||
_handleElement(child, {"subtype": BuiltInAttributeKey.quote}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -115,8 +116,10 @@ class HTMLToNodesConverter {
|
|||
return _handleContainer(childNodes);
|
||||
}
|
||||
|
||||
List<Node> _handleElement(html.Element element,
|
||||
[Map<String, dynamic>? attributes]) {
|
||||
List<Node> _handleElement(
|
||||
html.Element element, [
|
||||
Map<String, dynamic>? attributes,
|
||||
]) {
|
||||
if (element.localName == HTMLTag.h1) {
|
||||
return [_handleHeadingElement(element, HTMLTag.h1)];
|
||||
} else if (element.localName == HTMLTag.h2) {
|
||||
|
|
@ -142,8 +145,10 @@ class HTMLToNodesConverter {
|
|||
}
|
||||
}
|
||||
|
||||
Node _handleParagraph(html.Element element,
|
||||
[Map<String, dynamic>? attributes]) {
|
||||
Node _handleParagraph(
|
||||
html.Element element, [
|
||||
Map<String, dynamic>? attributes,
|
||||
]) {
|
||||
_inParagraph = true;
|
||||
final node = _handleRichText(element, attributes);
|
||||
_inParagraph = false;
|
||||
|
|
@ -171,7 +176,8 @@ class HTMLToNodesConverter {
|
|||
}
|
||||
|
||||
Attributes? _getDeltaAttributesFromHtmlAttributes(
|
||||
LinkedHashMap<Object, String> htmlAttributes) {
|
||||
LinkedHashMap<Object, String> htmlAttributes,
|
||||
) {
|
||||
final attrs = <String, dynamic>{};
|
||||
final styleString = htmlAttributes["style"];
|
||||
final cssMap = _cssStringToMap(styleString);
|
||||
|
|
@ -209,7 +215,7 @@ class HTMLToNodesConverter {
|
|||
return attrs.isEmpty ? null : attrs;
|
||||
}
|
||||
|
||||
_assignTextDecorations(Attributes attrs, String decorationStr) {
|
||||
void _assignTextDecorations(Attributes attrs, String decorationStr) {
|
||||
final decorations = decorationStr.split(" ");
|
||||
for (final d in decorations) {
|
||||
if (d == "line-through") {
|
||||
|
|
@ -220,7 +226,7 @@ class HTMLToNodesConverter {
|
|||
}
|
||||
}
|
||||
|
||||
_handleRichTextElement(Delta delta, html.Element element) {
|
||||
void _handleRichTextElement(Delta delta, html.Element element) {
|
||||
if (element.localName == HTMLTag.span) {
|
||||
delta.insert(
|
||||
element.text,
|
||||
|
|
@ -242,11 +248,18 @@ class HTMLToNodesConverter {
|
|||
attributes: {BuiltInAttributeKey.italic: true},
|
||||
);
|
||||
} else if (element.localName == HTMLTag.underline) {
|
||||
delta.insert(element.text,
|
||||
attributes: {BuiltInAttributeKey.underline: true});
|
||||
delta.insert(
|
||||
element.text,
|
||||
attributes: {BuiltInAttributeKey.underline: true},
|
||||
);
|
||||
} else if ([HTMLTag.italic, HTMLTag.em].contains(element.localName)) {
|
||||
delta
|
||||
.insert(element.text, attributes: {BuiltInAttributeKey.italic: true});
|
||||
} else if (element.localName == HTMLTag.del) {
|
||||
delta.insert(element.text,
|
||||
attributes: {BuiltInAttributeKey.strikethrough: true});
|
||||
delta.insert(
|
||||
element.text,
|
||||
attributes: {BuiltInAttributeKey.strikethrough: true},
|
||||
);
|
||||
} else if (element.localName == HTMLTag.code) {
|
||||
delta.insert(element.text, attributes: {BuiltInAttributeKey.code: true});
|
||||
} else {
|
||||
|
|
@ -258,8 +271,10 @@ class HTMLToNodesConverter {
|
|||
/// be regarded as a checkbox block.
|
||||
///
|
||||
/// A container contains a <img /> will be regarded as a image block
|
||||
Node _handleRichText(html.Element element,
|
||||
[Map<String, dynamic>? attributes]) {
|
||||
Node _handleRichText(
|
||||
html.Element element, [
|
||||
Map<String, dynamic>? attributes,
|
||||
]) {
|
||||
final image = element.querySelector(HTMLTag.image);
|
||||
if (image != null) {
|
||||
final imageNode = _handleImage(image);
|
||||
|
|
@ -284,13 +299,16 @@ class HTMLToNodesConverter {
|
|||
}
|
||||
}
|
||||
|
||||
final textNode = TextNode(delta: delta, attributes: {
|
||||
final textNode = TextNode(
|
||||
delta: delta,
|
||||
attributes: {
|
||||
if (attributes != null) ...attributes,
|
||||
if (isCheckbox) ...{
|
||||
BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox,
|
||||
BuiltInAttributeKey.checkbox: checked,
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
return textNode;
|
||||
}
|
||||
|
||||
|
|
@ -306,10 +324,12 @@ class HTMLToNodesConverter {
|
|||
List<Node> _handleUnorderedList(html.Element element) {
|
||||
final result = <Node>[];
|
||||
for (var child in element.children) {
|
||||
result.addAll(_handleListElement(
|
||||
result.addAll(
|
||||
_handleListElement(
|
||||
child,
|
||||
{"subtype": BuiltInAttributeKey.bulletedList},
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
@ -318,8 +338,12 @@ class HTMLToNodesConverter {
|
|||
final result = <Node>[];
|
||||
for (var i = 0; i < element.children.length; i++) {
|
||||
final child = element.children[i];
|
||||
result.addAll(_handleListElement(
|
||||
child, {"subtype": BuiltInAttributeKey.numberList, "number": i + 1}));
|
||||
result.addAll(
|
||||
_handleListElement(
|
||||
child,
|
||||
{"subtype": BuiltInAttributeKey.numberList, "number": i + 1},
|
||||
),
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
@ -332,11 +356,14 @@ class HTMLToNodesConverter {
|
|||
delta.insert(element.text);
|
||||
return TextNode(
|
||||
attributes: {"subtype": "heading", "heading": headingStyle},
|
||||
delta: delta);
|
||||
delta: delta,
|
||||
);
|
||||
}
|
||||
|
||||
List<Node> _handleListElement(html.Element element,
|
||||
[Map<String, dynamic>? attributes]) {
|
||||
List<Node> _handleListElement(
|
||||
html.Element element, [
|
||||
Map<String, dynamic>? attributes,
|
||||
]) {
|
||||
final result = <Node>[];
|
||||
final childNodes = element.nodes.toList();
|
||||
for (final child in childNodes) {
|
||||
|
|
@ -367,15 +394,19 @@ class NodesToHTMLConverter {
|
|||
/// This container is used to save the list elements temporarily.
|
||||
html.Element? _stashListContainer;
|
||||
|
||||
NodesToHTMLConverter(
|
||||
{required this.nodes, this.startOffset, this.endOffset}) {
|
||||
NodesToHTMLConverter({
|
||||
required this.nodes,
|
||||
this.startOffset,
|
||||
this.endOffset,
|
||||
}) {
|
||||
if (nodes.isEmpty) {
|
||||
return;
|
||||
} else if (nodes.length == 1) {
|
||||
final first = nodes.first;
|
||||
if (first is TextNode) {
|
||||
nodes[0] = first.copyWith(
|
||||
delta: first.delta.slice(startOffset ?? 0, endOffset));
|
||||
delta: first.delta.slice(startOffset ?? 0, endOffset),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
final first = nodes.first;
|
||||
|
|
@ -411,16 +442,17 @@ class NodesToHTMLConverter {
|
|||
return _result;
|
||||
}
|
||||
|
||||
_addTextNode(TextNode textNode, {int? end}) {
|
||||
void _addTextNode(TextNode textNode, {int? end}) {
|
||||
_addElement(textNode, _textNodeToHtml(textNode, end: end));
|
||||
}
|
||||
|
||||
_addElement(TextNode textNode, html.Element element) {
|
||||
void _addElement(TextNode textNode, html.Element element) {
|
||||
if (element.localName == HTMLTag.list) {
|
||||
final isNumbered =
|
||||
textNode.attributes["subtype"] == BuiltInAttributeKey.numberList;
|
||||
_stashListContainer ??= html.Element.tag(
|
||||
isNumbered ? HTMLTag.orderedList : HTMLTag.unorderedList);
|
||||
isNumbered ? HTMLTag.orderedList : HTMLTag.unorderedList,
|
||||
);
|
||||
_stashListContainer?.append(element);
|
||||
} else {
|
||||
if (_stashListContainer != null) {
|
||||
|
|
@ -434,18 +466,22 @@ class NodesToHTMLConverter {
|
|||
String toHTMLString() {
|
||||
final elements = toHTMLNodes();
|
||||
final copyString = elements.fold<String>(
|
||||
"", ((previousValue, element) => previousValue + stringify(element)));
|
||||
"",
|
||||
((previousValue, element) => previousValue + stringify(element)),
|
||||
);
|
||||
return copyString;
|
||||
}
|
||||
|
||||
html.Element _textNodeToHtml(TextNode textNode, {int? end}) {
|
||||
String? subType = textNode.attributes["subtype"];
|
||||
String? heading = textNode.attributes["heading"];
|
||||
return _deltaToHtml(textNode.delta,
|
||||
return _deltaToHtml(
|
||||
textNode.delta,
|
||||
subType: subType,
|
||||
heading: heading,
|
||||
end: end,
|
||||
checked: textNode.attributes["checkbox"] == true);
|
||||
checked: textNode.attributes["checkbox"] == true,
|
||||
);
|
||||
}
|
||||
|
||||
String _textDecorationsFromAttributes(Attributes attributes) {
|
||||
|
|
@ -515,8 +551,13 @@ class NodesToHTMLConverter {
|
|||
/// ```html
|
||||
/// <span style="...">Text</span>
|
||||
/// ```
|
||||
html.Element _deltaToHtml(Delta delta,
|
||||
{String? subType, String? heading, int? end, bool? checked}) {
|
||||
html.Element _deltaToHtml(
|
||||
Delta delta, {
|
||||
String? subType,
|
||||
String? heading,
|
||||
int? end,
|
||||
bool? checked,
|
||||
}) {
|
||||
if (end != null) {
|
||||
delta = delta.slice(0, end);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ class DocumentMarkdownDecoder extends Converter<String, Document> {
|
|||
BuiltInAttributeKey.subtype: BuiltInAttributeKey.quote,
|
||||
},
|
||||
);
|
||||
} else if (text.isNotEmpty && RegExp(r'^-*').stringMatch(text) == text) {
|
||||
} else if (text.isNotEmpty && RegExp('^-*').stringMatch(text) == text) {
|
||||
return Node(type: 'divider');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,13 +22,17 @@ Document markdownToDocument(
|
|||
/// Converts a [Document] to markdown.
|
||||
///
|
||||
/// [customParsers] is a list of custom parsers that will be used to parse the markdown.
|
||||
String documentToMarkdown(Document document,
|
||||
{List<NodeParser> customParsers = const []}) {
|
||||
return AppFlowyEditorMarkdownCodec(encodeParsers: [
|
||||
String documentToMarkdown(
|
||||
Document document, {
|
||||
List<NodeParser> customParsers = const [],
|
||||
}) {
|
||||
return AppFlowyEditorMarkdownCodec(
|
||||
encodeParsers: [
|
||||
...customParsers,
|
||||
const TextNodeParser(),
|
||||
const ImageNodeParser(),
|
||||
]).encode(document);
|
||||
],
|
||||
).encode(document);
|
||||
}
|
||||
|
||||
class AppFlowyEditorMarkdownCodec extends Codec<Document, String> {
|
||||
|
|
|
|||
|
|
@ -106,7 +106,10 @@ class _ColorPickerState extends State<ColorPicker> {
|
|||
}
|
||||
|
||||
Widget _buildColorItems(
|
||||
_ColorType type, List<ColorOption> options, String? selectedColor) {
|
||||
_ColorType type,
|
||||
List<ColorOption> options,
|
||||
String? selectedColor,
|
||||
) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ class _LinkMenuState extends State<LinkMenu> {
|
|||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: 350,
|
||||
child: Container(
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: style?.selectionMenuBackgroundColor ?? Colors.white,
|
||||
boxShadow: [
|
||||
|
|
|
|||
|
|
@ -79,7 +79,9 @@ class _FlowyRichTextState extends State<FlowyRichText> with SelectableMixin {
|
|||
|
||||
@override
|
||||
Position end() => Position(
|
||||
path: widget.textNode.path, offset: widget.textNode.toPlainText().length);
|
||||
path: widget.textNode.path,
|
||||
offset: widget.textNode.toPlainText().length,
|
||||
);
|
||||
|
||||
@override
|
||||
Rect? getCursorRectInPosition(Position position) {
|
||||
|
|
@ -92,7 +94,9 @@ class _FlowyRichTextState extends State<FlowyRichText> with SelectableMixin {
|
|||
cursorHeight =
|
||||
_placeholderRenderParagraph?.getFullHeightForCaret(textPosition);
|
||||
cursorOffset = _placeholderRenderParagraph?.getOffsetForCaret(
|
||||
textPosition, Rect.zero) ??
|
||||
textPosition,
|
||||
Rect.zero,
|
||||
) ??
|
||||
Offset.zero;
|
||||
}
|
||||
if (widget.cursorHeight != null && cursorHeight != null) {
|
||||
|
|
@ -139,8 +143,9 @@ class _FlowyRichTextState extends State<FlowyRichText> with SelectableMixin {
|
|||
|
||||
@override
|
||||
List<Rect> getRectsInSelection(Selection selection) {
|
||||
assert(selection.isSingle &&
|
||||
selection.start.path.equals(widget.textNode.path));
|
||||
assert(
|
||||
selection.isSingle && selection.start.path.equals(widget.textNode.path),
|
||||
);
|
||||
|
||||
final textSelection = TextSelection(
|
||||
baseOffset: selection.start.offset,
|
||||
|
|
@ -196,7 +201,9 @@ class _FlowyRichTextState extends State<FlowyRichText> with SelectableMixin {
|
|||
return RichText(
|
||||
key: _placeholderTextKey,
|
||||
textHeightBehavior: const TextHeightBehavior(
|
||||
applyHeightToFirstAscent: false, applyHeightToLastDescent: false),
|
||||
applyHeightToFirstAscent: false,
|
||||
applyHeightToLastDescent: false,
|
||||
),
|
||||
text: widget.placeholderTextSpanDecorator != null
|
||||
? widget.placeholderTextSpanDecorator!(textSpan)
|
||||
: textSpan,
|
||||
|
|
@ -303,7 +310,9 @@ class _FlowyRichTextState extends State<FlowyRichText> with SelectableMixin {
|
|||
}
|
||||
|
||||
GestureRecognizer _buildTapHrefGestureRecognizer(
|
||||
String href, Selection selection) {
|
||||
String href,
|
||||
Selection selection,
|
||||
) {
|
||||
Timer? timer;
|
||||
var tapCount = 0;
|
||||
final tapGestureRecognizer = TapGestureRecognizer()
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ class CursorWidgetState extends State<CursorWidget> {
|
|||
}
|
||||
|
||||
/// force the cursor widget to show for a while
|
||||
show() {
|
||||
void show() {
|
||||
setState(() {
|
||||
showCursor = true;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -40,10 +40,12 @@ class _SelectionMenuItemWidgetState extends State<SelectionMenuItemWidget> {
|
|||
style: ButtonStyle(
|
||||
alignment: Alignment.centerLeft,
|
||||
overlayColor: MaterialStateProperty.all(
|
||||
editorStyle.selectionMenuItemSelectedColor),
|
||||
editorStyle.selectionMenuItemSelectedColor,
|
||||
),
|
||||
backgroundColor: widget.isSelected
|
||||
? MaterialStateProperty.all(
|
||||
editorStyle.selectionMenuItemSelectedColor)
|
||||
editorStyle.selectionMenuItemSelectedColor,
|
||||
)
|
||||
: MaterialStateProperty.all(Colors.transparent),
|
||||
),
|
||||
label: Text(
|
||||
|
|
|
|||
|
|
@ -83,10 +83,13 @@ class SelectionMenu implements SelectionMenuService {
|
|||
_alignment = Alignment.topLeft;
|
||||
}
|
||||
_topLeft = offset;
|
||||
_offset = Offset(offset.dx,
|
||||
showBelow ? offset.dy : MediaQuery.of(context).size.height - offset.dy);
|
||||
_offset = Offset(
|
||||
offset.dx,
|
||||
showBelow ? offset.dy : MediaQuery.of(context).size.height - offset.dy,
|
||||
);
|
||||
|
||||
_selectionMenuEntry = OverlayEntry(builder: (context) {
|
||||
_selectionMenuEntry = OverlayEntry(
|
||||
builder: (context) {
|
||||
return Positioned(
|
||||
top: showBelow ? _offset.dy : null,
|
||||
bottom: showBelow ? null : _offset.dy,
|
||||
|
|
@ -111,7 +114,8 @@ class SelectionMenu implements SelectionMenuService {
|
|||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
Overlay.of(context).insert(_selectionMenuEntry!);
|
||||
|
||||
|
|
@ -241,7 +245,10 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
|
|||
];
|
||||
|
||||
Widget _selectionMenuIcon(
|
||||
String name, EditorState editorState, bool onSelected) {
|
||||
String name,
|
||||
EditorState editorState,
|
||||
bool onSelected,
|
||||
) {
|
||||
return FlowySvg(
|
||||
name: 'selection_menu/$name',
|
||||
color: onSelected
|
||||
|
|
|
|||
|
|
@ -119,7 +119,11 @@ class SelectionMenuItem {
|
|||
transaction
|
||||
..insertNode(path, node)
|
||||
..afterSelection = updateSelection?.call(
|
||||
editorState, path, bReplace, bInsertBefore) ??
|
||||
editorState,
|
||||
path,
|
||||
bReplace,
|
||||
bInsertBefore,
|
||||
) ??
|
||||
selection;
|
||||
|
||||
if (bReplace) {
|
||||
|
|
@ -216,7 +220,7 @@ class _SelectionMenuWidgetState extends State<SelectionMenuWidget> {
|
|||
return Focus(
|
||||
focusNode: _focusNode,
|
||||
onKey: _onKey,
|
||||
child: Container(
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: widget.editorState.editorStyle.selectionMenuBackgroundColor,
|
||||
boxShadow: [
|
||||
|
|
@ -248,24 +252,30 @@ class _SelectionMenuWidgetState extends State<SelectionMenuWidget> {
|
|||
List<Widget> itemWidgets = [];
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
if (i != 0 && i % (widget.maxItemInRow) == 0) {
|
||||
columns.add(Column(
|
||||
columns.add(
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: itemWidgets,
|
||||
));
|
||||
),
|
||||
);
|
||||
itemWidgets = [];
|
||||
}
|
||||
itemWidgets.add(SelectionMenuItemWidget(
|
||||
itemWidgets.add(
|
||||
SelectionMenuItemWidget(
|
||||
item: items[i],
|
||||
isSelected: selectedIndex == i,
|
||||
editorState: widget.editorState,
|
||||
menuService: widget.menuService,
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
if (itemWidgets.isNotEmpty) {
|
||||
columns.add(Column(
|
||||
columns.add(
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: itemWidgets,
|
||||
));
|
||||
),
|
||||
);
|
||||
itemWidgets = [];
|
||||
}
|
||||
return Row(
|
||||
|
|
|
|||
|
|
@ -118,7 +118,9 @@ class EditorStyle extends ThemeExtension<EditorStyle> {
|
|||
|
||||
@override
|
||||
ThemeExtension<EditorStyle> lerp(
|
||||
ThemeExtension<EditorStyle>? other, double t) {
|
||||
ThemeExtension<EditorStyle>? other,
|
||||
double t,
|
||||
) {
|
||||
if (other == null || other is! EditorStyle) {
|
||||
return this;
|
||||
}
|
||||
|
|
@ -129,21 +131,35 @@ class EditorStyle extends ThemeExtension<EditorStyle> {
|
|||
textPadding: EdgeInsets.lerp(textPadding, other.textPadding, t),
|
||||
selectionColor: Color.lerp(selectionColor, other.selectionColor, t),
|
||||
selectionMenuBackgroundColor: Color.lerp(
|
||||
selectionMenuBackgroundColor, other.selectionMenuBackgroundColor, t),
|
||||
selectionMenuBackgroundColor,
|
||||
other.selectionMenuBackgroundColor,
|
||||
t,
|
||||
),
|
||||
selectionMenuItemTextColor: Color.lerp(
|
||||
selectionMenuItemTextColor, other.selectionMenuItemTextColor, t),
|
||||
selectionMenuItemTextColor,
|
||||
other.selectionMenuItemTextColor,
|
||||
t,
|
||||
),
|
||||
selectionMenuItemIconColor: Color.lerp(
|
||||
selectionMenuItemIconColor, other.selectionMenuItemIconColor, t),
|
||||
selectionMenuItemIconColor,
|
||||
other.selectionMenuItemIconColor,
|
||||
t,
|
||||
),
|
||||
selectionMenuItemSelectedTextColor: Color.lerp(
|
||||
selectionMenuItemSelectedTextColor,
|
||||
other.selectionMenuItemSelectedTextColor,
|
||||
t),
|
||||
t,
|
||||
),
|
||||
selectionMenuItemSelectedIconColor: Color.lerp(
|
||||
selectionMenuItemSelectedIconColor,
|
||||
other.selectionMenuItemSelectedIconColor,
|
||||
t),
|
||||
selectionMenuItemSelectedColor: Color.lerp(selectionMenuItemSelectedColor,
|
||||
other.selectionMenuItemSelectedColor, t),
|
||||
t,
|
||||
),
|
||||
selectionMenuItemSelectedColor: Color.lerp(
|
||||
selectionMenuItemSelectedColor,
|
||||
other.selectionMenuItemSelectedColor,
|
||||
t,
|
||||
),
|
||||
textStyle: TextStyle.lerp(textStyle, other.textStyle, t),
|
||||
placeholderTextStyle:
|
||||
TextStyle.lerp(placeholderTextStyle, other.placeholderTextStyle, t),
|
||||
|
|
|
|||
|
|
@ -19,11 +19,17 @@ Iterable<ThemeExtension<dynamic>> get darkPluginStyleExtension => [
|
|||
];
|
||||
|
||||
typedef TextStyleCustomizer = TextStyle Function(
|
||||
EditorState editorState, TextNode textNode);
|
||||
EditorState editorState,
|
||||
TextNode textNode,
|
||||
);
|
||||
typedef PaddingCustomizer = EdgeInsets Function(
|
||||
EditorState editorState, TextNode textNode);
|
||||
EditorState editorState,
|
||||
TextNode textNode,
|
||||
);
|
||||
typedef IconCustomizer = Widget Function(
|
||||
EditorState editorState, TextNode textNode);
|
||||
EditorState editorState,
|
||||
TextNode textNode,
|
||||
);
|
||||
|
||||
class HeadingPluginStyle extends ThemeExtension<HeadingPluginStyle> {
|
||||
const HeadingPluginStyle({
|
||||
|
|
@ -47,7 +53,9 @@ class HeadingPluginStyle extends ThemeExtension<HeadingPluginStyle> {
|
|||
|
||||
@override
|
||||
ThemeExtension<HeadingPluginStyle> lerp(
|
||||
ThemeExtension<HeadingPluginStyle>? other, double t) {
|
||||
ThemeExtension<HeadingPluginStyle>? other,
|
||||
double t,
|
||||
) {
|
||||
if (other is! HeadingPluginStyle) {
|
||||
return this;
|
||||
}
|
||||
|
|
@ -105,7 +113,9 @@ class CheckboxPluginStyle extends ThemeExtension<CheckboxPluginStyle> {
|
|||
|
||||
@override
|
||||
ThemeExtension<CheckboxPluginStyle> lerp(
|
||||
ThemeExtension<CheckboxPluginStyle>? other, double t) {
|
||||
ThemeExtension<CheckboxPluginStyle>? other,
|
||||
double t,
|
||||
) {
|
||||
if (other is! CheckboxPluginStyle) {
|
||||
return this;
|
||||
}
|
||||
|
|
@ -167,7 +177,9 @@ class BulletedListPluginStyle extends ThemeExtension<BulletedListPluginStyle> {
|
|||
|
||||
@override
|
||||
ThemeExtension<BulletedListPluginStyle> lerp(
|
||||
ThemeExtension<BulletedListPluginStyle>? other, double t) {
|
||||
ThemeExtension<BulletedListPluginStyle>? other,
|
||||
double t,
|
||||
) {
|
||||
if (other is! BulletedListPluginStyle) {
|
||||
return this;
|
||||
}
|
||||
|
|
@ -194,7 +206,8 @@ class BulletedListPluginStyle extends ThemeExtension<BulletedListPluginStyle> {
|
|||
},
|
||||
);
|
||||
|
||||
static final dark = light.copyWith(icon: (_, __) {
|
||||
static final dark = light.copyWith(
|
||||
icon: (_, __) {
|
||||
const iconSize = Size.square(20.0);
|
||||
const iconPadding = EdgeInsets.only(right: 5.0);
|
||||
return FlowySvg(
|
||||
|
|
@ -204,7 +217,8 @@ class BulletedListPluginStyle extends ThemeExtension<BulletedListPluginStyle> {
|
|||
color: Colors.white,
|
||||
name: 'point',
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class NumberListPluginStyle extends ThemeExtension<NumberListPluginStyle> {
|
||||
|
|
@ -264,7 +278,8 @@ class NumberListPluginStyle extends ThemeExtension<NumberListPluginStyle> {
|
|||
},
|
||||
);
|
||||
|
||||
static final dark = light.copyWith(icon: (editorState, textNode) {
|
||||
static final dark = light.copyWith(
|
||||
icon: (editorState, textNode) {
|
||||
const iconPadding = EdgeInsets.only(left: 5.0, right: 5.0);
|
||||
return Container(
|
||||
padding: iconPadding,
|
||||
|
|
@ -276,7 +291,8 @@ class NumberListPluginStyle extends ThemeExtension<NumberListPluginStyle> {
|
|||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class QuotedTextPluginStyle extends ThemeExtension<QuotedTextPluginStyle> {
|
||||
|
|
@ -305,7 +321,9 @@ class QuotedTextPluginStyle extends ThemeExtension<QuotedTextPluginStyle> {
|
|||
|
||||
@override
|
||||
ThemeExtension<QuotedTextPluginStyle> lerp(
|
||||
ThemeExtension<QuotedTextPluginStyle>? other, double t) {
|
||||
ThemeExtension<QuotedTextPluginStyle>? other,
|
||||
double t,
|
||||
) {
|
||||
if (other is! QuotedTextPluginStyle) {
|
||||
return this;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@ import 'dart:io' show Platform;
|
|||
import 'package:flutter/material.dart' hide Overlay, OverlayEntry;
|
||||
|
||||
typedef ToolbarItemEventHandler = void Function(
|
||||
EditorState editorState, BuildContext context);
|
||||
EditorState editorState,
|
||||
BuildContext context,
|
||||
);
|
||||
typedef ToolbarItemValidator = bool Function(EditorState editorState);
|
||||
typedef ToolbarItemHighlightCallback = bool Function(EditorState editorState);
|
||||
|
||||
|
|
@ -413,7 +415,8 @@ void showLinkMenu(
|
|||
);
|
||||
}
|
||||
|
||||
_linkMenuOverlay = OverlayEntry(builder: (context) {
|
||||
_linkMenuOverlay = OverlayEntry(
|
||||
builder: (context) {
|
||||
return Positioned(
|
||||
top: matchRect.bottom + 5.0,
|
||||
left: matchRect.left,
|
||||
|
|
@ -457,7 +460,8 @@ void showLinkMenu(
|
|||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
Overlay.of(context)?.insert(_linkMenuOverlay!);
|
||||
|
||||
editorState.service.scrollService?.disable();
|
||||
|
|
@ -564,7 +568,8 @@ void showColorMenu(
|
|||
}
|
||||
|
||||
final style = editorState.editorStyle;
|
||||
_colorMenuOverlay = OverlayEntry(builder: (context) {
|
||||
_colorMenuOverlay = OverlayEntry(
|
||||
builder: (context) {
|
||||
return Positioned(
|
||||
top: matchRect.bottom + 5.0,
|
||||
left: matchRect.left + 10,
|
||||
|
|
@ -575,11 +580,13 @@ void showColorMenu(
|
|||
style.selectionMenuBackgroundColor ?? Colors.white,
|
||||
pickerItemHoverColor: style.selectionMenuItemSelectedColor ??
|
||||
Colors.blue.withOpacity(0.3),
|
||||
pickerItemTextColor: style.selectionMenuItemTextColor ?? Colors.black,
|
||||
pickerItemTextColor:
|
||||
style.selectionMenuItemTextColor ?? Colors.black,
|
||||
selectedFontColorHex: fontColorHex,
|
||||
selectedBackgroundColorHex: backgroundColorHex,
|
||||
fontColorOptions: _generateFontColorOptions(editorState),
|
||||
backgroundColorOptions: _generateBackgroundColorOptions(editorState),
|
||||
backgroundColorOptions:
|
||||
_generateBackgroundColorOptions(editorState),
|
||||
onSubmittedbackgroundColorHex: (color) {
|
||||
formatHighlightColor(
|
||||
editorState,
|
||||
|
|
@ -597,7 +604,8 @@ void showColorMenu(
|
|||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
Overlay.of(context)?.insert(_colorMenuOverlay!);
|
||||
|
||||
editorState.service.scrollService?.disable();
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue