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