feat: remove the reference of the deprecated values (#304)

* feat: remove the reference of the deprecated values

* chore: add migration guide
This commit is contained in:
Lucas.Xu 2023-07-12 12:21:54 +07:00 • committed by GitHub
commit 5f136df356
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 281 additions and 3459 deletions

View file

@ -115,6 +115,9 @@ Below are some examples of shortcut event customizations:
* [BIUS](https://github.com/AppFlowy-IO/appflowy-editor/tree/main/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/format_single_character) demonstrates how to make text bold/italic/underline/strikethrough through shortcut keys
* Need more examples? Check out [shortcuts](https://github.com/AppFlowy-IO/appflowy-editor/tree/main/lib/src/editor/editor_component/service/shortcuts)
## Migration Guide
Please refer to the [migration documentation]((https://github.com/AppFlowy-IO/appflowy-editor/blob/main/documentation/migration.md)).
## Glossary
Please refer to the API documentation.

View file

@ -10,7 +10,5 @@ linter:
- use_decorated_box
analyzer:
errors:
deprecated_member_use_from_same_package: ignore
exclude:
- lib/src/l10n/**

View file

@ -0,0 +1,9 @@
# Migration Guide
## From 1.1.0 to 1.2.0
- `AppFlowyEditor.custom` and `AppFlowyEditor.standard` have been removed. Use `AppFlowyEditor.` instead.
- For now, we provide the default values to the `blockComponentBuilders`, `characterShortcutEvents`, and `commandShortcutEvents` if you do not customize them.
- `DefaultSelectable` has been renamed to `DefaultSelectableMixin`
- `FlowyRichText` has been renamed to `AppFlowyRichText`.
- Added new parameter called `context`(BuildContext) into `TextSpanDecoratorForAttribute`.

View file

@ -1,20 +0,0 @@
import 'package:appflowy_editor/src/commands/command_extension.dart';
import 'package:appflowy_editor/src/core/document/attributes.dart';
import 'package:appflowy_editor/src/core/document/node.dart';
import 'package:appflowy_editor/src/core/document/path.dart';
import 'package:appflowy_editor/src/editor_state.dart';
extension TextCommands on EditorState {
Future<void> updateNodeAttributes(
Attributes attributes, {
Path? path,
Node? node,
}) {
return futureCommand(() {
final n = getNode(path: path, node: node);
apply(
transaction..updateNode(n, attributes),
);
});
}
}

View file

@ -1,82 +0,0 @@
import 'dart:async';
import 'package:appflowy_editor/src/core/document/node.dart';
import 'package:appflowy_editor/src/core/document/path.dart';
import 'package:appflowy_editor/src/core/location/selection.dart';
import 'package:appflowy_editor/src/editor_state.dart';
import 'package:flutter/widgets.dart';
extension CommandExtension on EditorState {
Future<void> futureCommand(void Function() fn) async {
final completer = Completer<void>();
fn();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
completer.complete();
});
return completer.future;
}
Node getNode({
Path? path,
Node? node,
}) {
if (node != null) {
return node;
} else if (path != null) {
return document.nodeAtPath(path)!;
}
throw Exception('path and node cannot be null at the same time');
}
TextNode getTextNode({
Path? path,
TextNode? textNode,
}) {
if (textNode != null) {
return textNode;
} else if (path != null) {
return document.nodeAtPath(path)! as TextNode;
}
throw Exception('path and node cannot be null at the same time');
}
Selection getSelection(
Selection? selection,
) {
final currentSelection = service.selectionService.currentSelection.value;
if (selection != null) {
return selection;
} else if (currentSelection != null) {
return currentSelection;
}
throw Exception('path and textNode cannot be null at the same time');
}
List<String> getTextInSelection(
List<TextNode> textNodes,
Selection selection,
) {
List<String> res = [];
if (selection.isSingle) {
final plainText = textNodes.first.toPlainText();
res.add(plainText.substring(selection.startIndex, selection.endIndex));
} else if (!selection.isCollapsed) {
for (var i = 0; i < textNodes.length; i++) {
final plainText = textNodes[i].toPlainText();
if (i == 0) {
res.add(
plainText.substring(
selection.startIndex,
plainText.length,
),
);
} else if (i == textNodes.length - 1) {
res.add(plainText.substring(0, selection.endIndex));
} else {
res.add(plainText);
}
}
}
return res;
}
}

View file

@ -1,123 +0,0 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/commands/command_extension.dart';
extension TextCommands on EditorState {
/// Insert text at the given index of the given [TextNode] or the [Path].
///
/// [Path] and [TextNode] are mutually exclusive.
/// One of these two parameters must have a value.
Future<void> insertText(
int index,
String text, {
Path? path,
TextNode? textNode,
}) async {
final n = getTextNode(path: path, textNode: textNode);
return apply(
transaction..insertText(n, index, text),
);
}
Future<void> insertTextAtCurrentSelection(String text) async {
final selection = getSelection(null);
assert(selection.isCollapsed);
final textNode = getTextNode(path: selection.start.path);
return insertText(
selection.startIndex,
text,
textNode: textNode,
);
}
Future<void> formatText(
Selection? selection,
Attributes attributes, {
Path? path,
TextNode? textNode,
}) async {
final n = getTextNode(path: path, textNode: textNode);
final s = getSelection(selection);
return apply(
transaction..formatText(n, s.startIndex, s.length, attributes),
);
}
Future<void> formatTextWithBuiltInAttribute(
String key,
Attributes attributes, {
Selection? selection,
Path? path,
TextNode? textNode,
}) async {
final n = getTextNode(path: path, textNode: textNode);
if (BuiltInAttributeKey.globalStyleKeys.contains(key)) {
final attr = n.attributes
..removeWhere(
(key, _) => BuiltInAttributeKey.globalStyleKeys.contains(key),
)
..addAll(attributes)
..addAll({
BuiltInAttributeKey.subtype: key,
});
return apply(
transaction..updateNode(n, attr),
);
} else if (BuiltInAttributeKey.partialStyleKeys.contains(key)) {
final s = getSelection(selection);
return apply(
transaction..formatText(n, s.startIndex, s.length, attributes),
);
}
}
Future<void> formatTextToCheckbox(
bool check, {
Path? path,
TextNode? textNode,
}) async {
return formatTextWithBuiltInAttribute(
BuiltInAttributeKey.checkbox,
{BuiltInAttributeKey.checkbox: check},
path: path,
textNode: textNode,
);
}
Future<void> formatLinkInText(
String? link, {
Path? path,
TextNode? textNode,
}) async {
return formatTextWithBuiltInAttribute(
BuiltInAttributeKey.href,
{BuiltInAttributeKey.href: link},
path: path,
textNode: textNode,
);
}
// Future<void> insertNewLine({
// Path? path,
// }) async {
// final p = path ?? getSelection(null).start.path.next;
// final transaction = this.transaction;
// transaction.insertNode(p, TextNode.empty());
// transaction.afterSelection = Selection.single(
// path: p,
// startOffset: 0,
// );
// return apply(transaction);
// }
Future<void> insertNewLineAtCurrentSelection() async {
final selection = getSelection(null);
assert(selection.isCollapsed);
final textNode = getTextNode(path: selection.start.path);
final transaction = this.transaction;
transaction.splitText(
textNode,
selection.startIndex,
);
return apply(transaction);
}
}

View file

@ -119,16 +119,17 @@ class Document {
return true;
}
/// Updates the [TextNode] at the given [Path]
/// Updates the [Node] with [Delta] at the given [Path]
bool updateText(Path path, Delta delta) {
if (path.isEmpty) {
return false;
}
final target = nodeAtPath(path);
if (target == null || target is! TextNode) {
final targetDelta = target?.delta;
if (target == null || targetDelta == null) {
return false;
}
target.delta = target.delta.compose(delta);
target.updateAttributes({'delta': (targetDelta.compose(delta)).toJson()});
return true;
}

View file

@ -125,12 +125,6 @@ class Transaction {
insertNode(path, node, deepCopy: false);
}
/// Update the [TextNode]s with the given [Delta].
void updateText(TextNode textNode, Delta delta) {
final inverted = delta.invert(textNode.delta);
add(UpdateTextOperation(textNode.path, delta, inverted));
}
/// Returns the JSON representation of the transaction.
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@ -551,126 +545,6 @@ extension TextTransaction on Transaction {
markNeedsComposing = true;
_composeMap.putIfAbsent(node, () => []).add(delta);
}
// the below code is deprecated.
void splitText(TextNode textNode, int offset) {
final delta = textNode.delta;
final second = delta.slice(offset, delta.length);
final path = textNode.path.next;
deleteText(textNode, offset, delta.length);
insertNode(
path,
TextNode(
attributes: textNode.attributes,
delta: second,
),
);
afterSelection = Selection.collapsed(
Position(
path: path,
offset: 0,
),
);
}
/// Inserts the text content at a specified index.
///
/// Optionally, you may specify formatting attributes that are applied to the inserted string.
/// By default, the formatting attributes before the insert position will be reused.
// void insertText(
// TextNode textNode,
// int index,
// String text, {
// Attributes? attributes,
// }) {
// var newAttributes = attributes;
// if (index != 0 && attributes == null) {
// newAttributes =
// textNode.delta.slice(max(index - 1, 0), index).first.attributes;
// if (newAttributes != null) {
// newAttributes = {...newAttributes}; // make a copy
// }
// }
// updateText(
// textNode,
// Delta()
// ..retain(index)
// ..insert(text, attributes: newAttributes),
// );
// afterSelection = Selection.collapsed(
// Position(path: textNode.path, offset: index + text.length),
// );
// }
/// Assigns a formatting attributes to a range of text.
// void formatText(
// TextNode textNode,
// int index,
// int length,
// Attributes attributes,
// ) {
// afterSelection = beforeSelection;
// updateText(
// textNode,
// Delta()
// ..retain(index)
// ..retain(length, attributes: attributes),
// );
// }
// /// Deletes the text of specified length starting at index.
// void deleteText(
// TextNode textNode,
// int index,
// int length,
// ) {
// updateText(
// textNode,
// Delta()
// ..retain(index)
// ..delete(length),
// );
// afterSelection = Selection.collapsed(
// Position(path: textNode.path, offset: index),
// );
// }
/// Replaces the text of specified length starting at index.
///
/// Optionally, you may specify formatting attributes that are applied to the inserted string.
/// By default, the formatting attributes before the insert position will be reused.
// void replaceText(
// TextNode textNode,
// int index,
// int length,
// String text, {
// Attributes? attributes,
// }) {
// var newAttributes = attributes;
// if (index != 0 && attributes == null) {
// newAttributes =
// textNode.delta.slice(max(index - 1, 0), index).first.attributes;
// if (newAttributes == null) {
// final slicedDelta = textNode.delta.slice(index, index + length);
// if (slicedDelta.isNotEmpty) {
// newAttributes = slicedDelta.first.attributes;
// }
// }
// }
// updateText(
// textNode,
// Delta()
// ..retain(index)
// ..delete(length)
// ..insert(text, attributes: {...newAttributes ?? {}}),
// );
// afterSelection = Selection.collapsed(
// Position(
// path: textNode.path,
// offset: index + text.length,
// ),
// );
// }
}
extension on Delta {

View file

@ -20,13 +20,12 @@ Node paragraphNode({
Attributes? attributes,
Iterable<Node> children = const [],
}) {
attributes ??= {
ParagraphBlockKeys.delta: (delta ?? (Delta()..insert(text ?? ''))).toJson(),
};
return Node(
type: ParagraphBlockKeys.type,
attributes: {
...attributes,
ParagraphBlockKeys.delta:
(delta ?? (Delta()..insert(text ?? ''))).toJson(),
if (attributes != null) ...attributes,
if (textDirection != null)
ParagraphBlockKeys.textDirection: textDirection,
},

View file

@ -289,7 +289,7 @@ class _DesktopSelectionServiceWidgetState
final selection = currentSelection.value;
if (selection == null ||
(selection.isCollapsed == true &&
currentSelectedNodes.first is TextNode)) {
currentSelectedNodes.first.delta != null)) {
_onDoubleTapDown(details);
}
@ -529,7 +529,7 @@ class _DesktopSelectionServiceWidgetState
_clearContextMenu();
// For now, only support the text node.
if (!currentSelectedNodes.every((element) => element is TextNode)) {
if (!currentSelectedNodes.every((element) => element.delta != null)) {
return;
}

View file

@ -31,16 +31,15 @@ CommandShortcutEventHandler _copyCommandHandler = (editorState) {
// rich text.
// TODO: support rich text. the below code is not working.
final nodes = editorState.getNodesInSelection(selection);
final html = NodesToHTMLConverter(
nodes: nodes,
startOffset: selection.startIndex,
endOffset: selection.endIndex,
).toHTMLString();
// final nodes = editorState.getNodesInSelection(selection);
// final html = NodesToHTMLConverter(
// nodes: nodes,
// startOffset: selection.startIndex,
// endOffset: selection.endIndex,
// ).toHTMLString();
AppFlowyClipboard.setData(
text: text,
html: html.isEmpty ? null : html,
);
return KeyEventResult.handled;

View file

@ -1,8 +0,0 @@
import 'package:appflowy_editor/appflowy_editor.dart';
extension EditorStateExtensions on EditorState {
List<TextNode> get selectedTextNodes =>
service.selectionService.currentSelectedNodes
.whereType<TextNode>()
.toList(growable: false);
}

View file

@ -1,9 +1,7 @@
export 'attributes_extension.dart';
export 'color_extension.dart';
export 'editor_state_extensions.dart';
export 'node_extensions.dart';
export 'object_extensions.dart';
export 'position_extension.dart';
export 'text_node_extensions.dart';
export 'text_style_extension.dart';
export 'url_launcher_extension.dart';

View file

@ -1,221 +0,0 @@
import 'package:appflowy_editor/src/core/document/node.dart';
import 'package:appflowy_editor/src/core/document/path.dart';
import 'package:appflowy_editor/src/core/location/position.dart';
import 'package:appflowy_editor/src/core/location/selection.dart';
import 'package:appflowy_editor/src/core/document/text_delta.dart';
import 'package:appflowy_editor/src/core/legacy/built_in_attribute_keys.dart';
extension TextNodeExtension on TextNode {
T? getAttributeInSelection<T>(Selection selection, String styleKey) {
final ops = delta.whereType<TextInsert>();
final startOffset =
selection.isBackward ? selection.start.offset : selection.end.offset;
final endOffset =
selection.isBackward ? selection.end.offset : selection.start.offset;
var start = 0;
for (final op in ops) {
if (start >= endOffset) {
break;
}
final length = op.length;
if (start < endOffset && start + length > startOffset) {
final attributes = op.attributes;
if (attributes != null && attributes[styleKey] is T?) {
return attributes[styleKey];
}
}
start += length;
}
return null;
}
bool allSatisfyLinkInSelection(Selection selection) =>
allSatisfyInSelection(selection, BuiltInAttributeKey.href, (value) {
return value != null;
});
bool allSatisfyFontColorInSelection(Selection selection) =>
allSatisfyInSelection(selection, BuiltInAttributeKey.textColor, (value) {
return value != null;
});
bool allSatisfyBackgroundColorInSelection(Selection selection) =>
allSatisfyInSelection(selection, BuiltInAttributeKey.highlightColor,
(value) {
return value != null;
});
bool allSatisfyBoldInSelection(Selection selection) =>
allSatisfyInSelection(selection, BuiltInAttributeKey.bold, (value) {
return value == true;
});
bool allSatisfyItalicInSelection(Selection selection) =>
allSatisfyInSelection(selection, BuiltInAttributeKey.italic, (value) {
return value == true;
});
bool allSatisfyUnderlineInSelection(Selection selection) =>
allSatisfyInSelection(selection, BuiltInAttributeKey.underline, (value) {
return value == true;
});
bool allSatisfyStrikethroughInSelection(Selection selection) =>
allSatisfyInSelection(selection, BuiltInAttributeKey.strikethrough,
(value) {
return value == true;
});
bool allSatisfyCodeInSelection(Selection selection) =>
allSatisfyInSelection(selection, BuiltInAttributeKey.code, (value) {
return value == true;
});
bool allSatisfyInSelection(
Selection selection,
String styleKey,
bool Function(dynamic value) test,
) {
if (BuiltInAttributeKey.globalStyleKeys.contains(styleKey)) {
if (attributes.containsKey(styleKey)) {
return test(attributes[styleKey]);
}
} else if (BuiltInAttributeKey.partialStyleKeys.contains(styleKey)) {
final ops = delta.whereType<TextInsert>();
final startOffset =
selection.isBackward ? selection.start.offset : selection.end.offset;
final endOffset =
selection.isBackward ? selection.end.offset : selection.start.offset;
var start = 0;
for (final op in ops) {
if (start >= endOffset) {
break;
}
final length = op.length;
if (start < endOffset && start + length > startOffset) {
if (op.attributes == null ||
!op.attributes!.containsKey(styleKey) ||
!test(op.attributes![styleKey])) {
return false;
}
}
start += length;
}
return true;
}
return false;
}
bool allNotSatisfyInSelection(
String styleKey,
dynamic value,
Selection selection,
) {
final ops = delta.whereType<TextInsert>();
final startOffset =
selection.isBackward ? selection.start.offset : selection.end.offset;
final endOffset =
selection.isBackward ? selection.end.offset : selection.start.offset;
var start = 0;
for (final op in ops) {
if (start >= endOffset) {
break;
}
final length = op.length;
if (start < endOffset && start + length > startOffset) {
if (op.attributes != null &&
op.attributes!.containsKey(styleKey) &&
op.attributes![styleKey] == value) {
return false;
}
}
start += length;
}
return true;
}
bool get isNotBulletOrCheckbox => ![
BuiltInAttributeKey.bulletedList,
BuiltInAttributeKey.checkbox
].contains('');
}
extension TextNodesExtension on List<TextNode> {
bool allSatisfyBoldInSelection(Selection selection) => allSatisfyInSelection(
selection,
BuiltInAttributeKey.bold,
(value) => value == true,
);
bool allSatisfyItalicInSelection(Selection selection) =>
allSatisfyInSelection(
selection,
BuiltInAttributeKey.italic,
(value) => value == true,
);
bool allSatisfyUnderlineInSelection(Selection selection) =>
allSatisfyInSelection(
selection,
BuiltInAttributeKey.underline,
(value) => value == true,
);
bool allSatisfyStrikethroughInSelection(Selection selection) =>
allSatisfyInSelection(
selection,
BuiltInAttributeKey.strikethrough,
(value) => value == true,
);
bool allSatisfyInSelection(
Selection selection,
String styleKey,
bool Function(dynamic value) test,
) {
if (isEmpty) {
return false;
}
if (length == 1) {
return first.allSatisfyInSelection(selection, styleKey, (value) {
return test(value);
});
} else {
for (var i = 0; i < length; i++) {
final node = this[i];
final Selection newSelection;
if (i == 0 && node.path.equals(selection.start.path)) {
if (selection.isBackward) {
newSelection = selection.copyWith(
end: Position(path: node.path, offset: node.toPlainText().length),
);
} else {
newSelection = selection.copyWith(
end: Position(path: node.path, offset: 0),
);
}
} else if (i == length - 1 && node.path.equals(selection.end.path)) {
if (selection.isBackward) {
newSelection = selection.copyWith(
start: Position(path: node.path, offset: 0),
);
} else {
newSelection = selection.copyWith(
start:
Position(path: node.path, offset: node.toPlainText().length),
);
}
} else {
newSelection = Selection(
start: Position(path: node.path, offset: 0),
end: Position(path: node.path, offset: node.toPlainText().length),
);
}
if (!node.allSatisfyInSelection(newSelection, styleKey, test)) {
return false;
}
}
return true;
}
}
}

View file

@ -1,8 +1,6 @@
import 'dart:collection';
import 'dart:ui';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
import 'package:html/parser.dart' show parse;
import 'package:html/dom.dart' as html;
@ -400,7 +398,7 @@ class NodesToHTMLConverter {
/// </ul>
///
/// This container is used to save the list elements temporarily.
html.Element? _stashListContainer;
// html.Element? _stashListContainer;
NodesToHTMLConverter({
required this.nodes,
@ -411,72 +409,63 @@ class NodesToHTMLConverter {
return;
} else if (nodes.length == 1) {
final first = nodes.first;
if (first is TextNode) {
if (first.delta != null) {
nodes[0] = first.copyWith(
delta: first.delta.slice(startOffset ?? 0, endOffset),
attributes: {
...first.attributes,
'delta': (first.delta!.slice(startOffset ?? 0, endOffset)).toJson(),
},
);
}
} else {
final first = nodes.first;
final last = nodes.last;
if (first is TextNode) {
nodes[0] = first.copyWith(delta: first.delta.slice(startOffset ?? 0));
if (first.delta != null) {
nodes[0] = first.copyWith(
attributes: {
...first.attributes,
'delta': (first.delta!.slice(startOffset ?? 0)).toJson(),
},
);
}
if (last is TextNode) {
nodes[nodes.length - 1] =
last.copyWith(delta: last.delta.slice(0, endOffset));
if (last.delta != null) {
nodes[nodes.length - 1] = first.copyWith(
attributes: {
...first.attributes,
'delta': (last.delta!.slice(0, endOffset)).toJson(),
},
);
}
}
}
List<html.Node> toHTMLNodes() {
for (final node in nodes) {
if (node.type == 'text') {
final textNode = node as TextNode;
if (node == nodes.first) {
_addTextNode(textNode);
} else if (node == nodes.last) {
_addTextNode(textNode, end: endOffset);
} else {
_addTextNode(textNode);
}
} else if (node.type == 'image') {
final textNode = node;
final anchor = html.Element.tag(HTMLTag.image);
anchor.attributes['src'] = textNode.attributes['image_src'];
// for (final node in nodes) {
// if (node.type == 'text') {
// final textNode = node as TextNode;
// if (node == nodes.first) {
// _addTextNode(textNode);
// } else if (node == nodes.last) {
// _addTextNode(textNode, end: endOffset);
// } else {
// _addTextNode(textNode);
// }
// } else if (node.type == 'image') {
// final textNode = node;
// final anchor = html.Element.tag(HTMLTag.image);
// anchor.attributes['src'] = textNode.attributes['image_src'];
_result.add(anchor);
}
// TODO: handle image and other blocks
}
if (_stashListContainer != null) {
_result.add(_stashListContainer!);
_stashListContainer = null;
}
// _result.add(anchor);
// }
// // TODO: handle image and other blocks
// }
// if (_stashListContainer != null) {
// _result.add(_stashListContainer!);
// _stashListContainer = null;
// }
return _result;
}
void _addTextNode(TextNode textNode, {int? end}) {
_addElement(textNode, _textNodeToHtml(textNode, end: end));
}
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,
);
_stashListContainer?.append(element);
} else {
if (_stashListContainer != null) {
_result.add(_stashListContainer!);
_stashListContainer = null;
}
_result.add(element);
}
}
String toHTMLString() {
final elements = toHTMLNodes();
final copyString = elements.fold<String>(
@ -486,68 +475,56 @@ class NodesToHTMLConverter {
return copyString;
}
html.Element _textNodeToHtml(TextNode textNode, {int? end}) {
String? subType = textNode.attributes['subtype'];
String? heading = textNode.attributes['heading'];
return _deltaToHtml(
textNode.delta,
subType: subType,
heading: heading,
end: end,
checked: textNode.attributes['checkbox'] == true,
);
}
// String _textDecorationsFromAttributes(Attributes attributes) {
// var textDecoration = <String>[];
// if (attributes[BuiltInAttributeKey.strikethrough] == true) {
// textDecoration.add('line-through');
// }
// if (attributes[BuiltInAttributeKey.underline] == true) {
// textDecoration.add('underline');
// }
String _textDecorationsFromAttributes(Attributes attributes) {
var textDecoration = <String>[];
if (attributes[BuiltInAttributeKey.strikethrough] == true) {
textDecoration.add('line-through');
}
if (attributes[BuiltInAttributeKey.underline] == true) {
textDecoration.add('underline');
}
// return textDecoration.join(' ');
// }
return textDecoration.join(' ');
}
// String _attributesToCssStyle(Map<String, dynamic> attributes) {
// final cssMap = <String, String>{};
// if (attributes[BuiltInAttributeKey.highlightColor] != null) {
// final color = Color(
// int.parse(attributes[BuiltInAttributeKey.highlightColor]),
// );
// cssMap['background-color'] = color.toRgbaString();
// }
// if (attributes[BuiltInAttributeKey.textColor] != null) {
// final color = Color(
// int.parse(attributes[BuiltInAttributeKey.textColor]),
// );
// cssMap['color'] = color.toRgbaString();
// }
// if (attributes[BuiltInAttributeKey.bold] == true) {
// cssMap['font-weight'] = 'bold';
// }
String _attributesToCssStyle(Map<String, dynamic> attributes) {
final cssMap = <String, String>{};
if (attributes[BuiltInAttributeKey.highlightColor] != null) {
final color = Color(
int.parse(attributes[BuiltInAttributeKey.highlightColor]),
);
cssMap['background-color'] = color.toRgbaString();
}
if (attributes[BuiltInAttributeKey.textColor] != null) {
final color = Color(
int.parse(attributes[BuiltInAttributeKey.textColor]),
);
cssMap['color'] = color.toRgbaString();
}
if (attributes[BuiltInAttributeKey.bold] == true) {
cssMap['font-weight'] = 'bold';
}
// final textDecoration = _textDecorationsFromAttributes(attributes);
// if (textDecoration.isNotEmpty) {
// cssMap['text-decoration'] = textDecoration;
// }
final textDecoration = _textDecorationsFromAttributes(attributes);
if (textDecoration.isNotEmpty) {
cssMap['text-decoration'] = textDecoration;
}
// if (attributes[BuiltInAttributeKey.italic] == true) {
// cssMap['font-style'] = 'italic';
// }
// return _cssMapToCssStyle(cssMap);
// }
if (attributes[BuiltInAttributeKey.italic] == true) {
cssMap['font-style'] = 'italic';
}
return _cssMapToCssStyle(cssMap);
}
String _cssMapToCssStyle(Map<String, String> cssMap) {
return cssMap.entries.fold('', (previousValue, element) {
final kv = '${element.key}: ${element.value}';
if (previousValue.isEmpty) {
return kv;
}
return '$previousValue; $kv';
});
}
// String _cssMapToCssStyle(Map<String, String> cssMap) {
// return cssMap.entries.fold('', (previousValue, element) {
// final kv = '${element.key}: ${element.value}';
// if (previousValue.isEmpty) {
// return kv;
// }
// return '$previousValue; $kv';
// });
// }
/// Convert the rich text to HTML
///
@ -565,115 +542,115 @@ class NodesToHTMLConverter {
/// ```html
/// <span style='...'>Text</span>
/// ```
html.Element _deltaToHtml(
Delta delta, {
String? subType,
String? heading,
int? end,
bool? checked,
}) {
if (end != null) {
delta = delta.slice(0, end);
}
// html.Element _deltaToHtml(
// Delta delta, {
// String? subType,
// String? heading,
// int? end,
// bool? checked,
// }) {
// if (end != null) {
// delta = delta.slice(0, end);
// }
final childNodes = <html.Node>[];
String tagName = HTMLTag.paragraph;
// final childNodes = <html.Node>[];
// String tagName = HTMLTag.paragraph;
if (subType == BuiltInAttributeKey.bulletedList ||
subType == BuiltInAttributeKey.numberList) {
tagName = HTMLTag.list;
} else if (subType == BuiltInAttributeKey.checkbox) {
final node = html.Element.html('<input type="checkbox" />');
if (checked != null && checked) {
node.attributes['checked'] = 'true';
}
childNodes.add(node);
} else if (subType == BuiltInAttributeKey.heading) {
if (heading == BuiltInAttributeKey.h1) {
tagName = HTMLTag.h1;
} else if (heading == BuiltInAttributeKey.h2) {
tagName = HTMLTag.h2;
} else if (heading == BuiltInAttributeKey.h3) {
tagName = HTMLTag.h3;
}
} else if (subType == BuiltInAttributeKey.quote) {
tagName = HTMLTag.blockQuote;
}
// if (subType == BuiltInAttributeKey.bulletedList ||
// subType == BuiltInAttributeKey.numberList) {
// tagName = HTMLTag.list;
// } else if (subType == BuiltInAttributeKey.checkbox) {
// final node = html.Element.html('<input type="checkbox" />');
// if (checked != null && checked) {
// node.attributes['checked'] = 'true';
// }
// childNodes.add(node);
// } else if (subType == BuiltInAttributeKey.heading) {
// if (heading == BuiltInAttributeKey.h1) {
// tagName = HTMLTag.h1;
// } else if (heading == BuiltInAttributeKey.h2) {
// tagName = HTMLTag.h2;
// } else if (heading == BuiltInAttributeKey.h3) {
// tagName = HTMLTag.h3;
// }
// } else if (subType == BuiltInAttributeKey.quote) {
// tagName = HTMLTag.blockQuote;
// }
for (final op in delta) {
if (op is TextInsert) {
final attributes = op.attributes;
if (attributes != null) {
if (attributes.length == 1 &&
attributes[BuiltInAttributeKey.bold] == true) {
final strong = html.Element.tag(HTMLTag.strong);
strong.append(html.Text(op.text));
childNodes.add(strong);
} else if (attributes.length == 1 &&
attributes[BuiltInAttributeKey.underline] == true) {
final strong = html.Element.tag(HTMLTag.underline);
strong.append(html.Text(op.text));
childNodes.add(strong);
} else if (attributes.length == 1 &&
attributes[BuiltInAttributeKey.italic] == true) {
final strong = html.Element.tag(HTMLTag.italic);
strong.append(html.Text(op.text));
childNodes.add(strong);
} else if (attributes.length == 1 &&
attributes[BuiltInAttributeKey.strikethrough] == true) {
final strong = html.Element.tag(HTMLTag.del);
strong.append(html.Text(op.text));
childNodes.add(strong);
} else if (attributes.length == 1 &&
attributes[BuiltInAttributeKey.code] == true) {
final code = html.Element.tag(HTMLTag.code);
code.append(html.Text(op.text));
childNodes.add(code);
} else if (attributes.length == 1 &&
attributes[BuiltInAttributeKey.href] != null) {
final anchor = html.Element.tag(HTMLTag.anchor);
anchor.attributes['href'] = attributes[BuiltInAttributeKey.href];
anchor.append(html.Text(op.text));
childNodes.add(anchor);
} else {
final span = html.Element.tag(HTMLTag.span);
final cssString = _attributesToCssStyle(attributes);
if (cssString.isNotEmpty) {
span.attributes['style'] = cssString;
}
span.append(html.Text(op.text));
childNodes.add(span);
}
} else {
childNodes.add(html.Text(op.text));
}
}
}
// for (final op in delta) {
// if (op is TextInsert) {
// final attributes = op.attributes;
// if (attributes != null) {
// if (attributes.length == 1 &&
// attributes[BuiltInAttributeKey.bold] == true) {
// final strong = html.Element.tag(HTMLTag.strong);
// strong.append(html.Text(op.text));
// childNodes.add(strong);
// } else if (attributes.length == 1 &&
// attributes[BuiltInAttributeKey.underline] == true) {
// final strong = html.Element.tag(HTMLTag.underline);
// strong.append(html.Text(op.text));
// childNodes.add(strong);
// } else if (attributes.length == 1 &&
// attributes[BuiltInAttributeKey.italic] == true) {
// final strong = html.Element.tag(HTMLTag.italic);
// strong.append(html.Text(op.text));
// childNodes.add(strong);
// } else if (attributes.length == 1 &&
// attributes[BuiltInAttributeKey.strikethrough] == true) {
// final strong = html.Element.tag(HTMLTag.del);
// strong.append(html.Text(op.text));
// childNodes.add(strong);
// } else if (attributes.length == 1 &&
// attributes[BuiltInAttributeKey.code] == true) {
// final code = html.Element.tag(HTMLTag.code);
// code.append(html.Text(op.text));
// childNodes.add(code);
// } else if (attributes.length == 1 &&
// attributes[BuiltInAttributeKey.href] != null) {
// final anchor = html.Element.tag(HTMLTag.anchor);
// anchor.attributes['href'] = attributes[BuiltInAttributeKey.href];
// anchor.append(html.Text(op.text));
// childNodes.add(anchor);
// } else {
// final span = html.Element.tag(HTMLTag.span);
// final cssString = _attributesToCssStyle(attributes);
// if (cssString.isNotEmpty) {
// span.attributes['style'] = cssString;
// }
// span.append(html.Text(op.text));
// childNodes.add(span);
// }
// } else {
// childNodes.add(html.Text(op.text));
// }
// }
// }
if (tagName == HTMLTag.blockQuote) {
final p = html.Element.tag(HTMLTag.paragraph);
for (final node in childNodes) {
p.append(node);
}
final blockQuote = html.Element.tag(tagName);
blockQuote.append(p);
return blockQuote;
} else if (!HTMLTag.isTopLevel(tagName)) {
final p = html.Element.tag(HTMLTag.paragraph);
for (final node in childNodes) {
p.append(node);
}
final result = html.Element.tag(HTMLTag.list);
result.append(p);
return result;
} else {
final p = html.Element.tag(tagName);
for (final node in childNodes) {
p.append(node);
}
return p;
}
}
// if (tagName == HTMLTag.blockQuote) {
// final p = html.Element.tag(HTMLTag.paragraph);
// for (final node in childNodes) {
// p.append(node);
// }
// final blockQuote = html.Element.tag(tagName);
// blockQuote.append(p);
// return blockQuote;
// } else if (!HTMLTag.isTopLevel(tagName)) {
// final p = html.Element.tag(HTMLTag.paragraph);
// for (final node in childNodes) {
// p.append(node);
// }
// final result = html.Element.tag(HTMLTag.list);
// result.append(p);
// return result;
// } else {
// final p = html.Element.tag(tagName);
// for (final node in childNodes) {
// p.append(node);
// }
// return p;
// }
// }
}
String stringify(html.Node node) {

View file

@ -1,46 +0,0 @@
import 'package:appflowy_editor/src/core/document/node.dart';
class Infra {
// find the forward nearest text node
static TextNode? forwardNearestTextNode(Node node) {
var previous = node.previous;
while (previous != null) {
final lastTextNode = findLastTextNode(previous);
if (lastTextNode != null) {
return lastTextNode;
}
if (previous is TextNode) {
return previous;
}
previous = previous.previous;
}
final parent = node.parent;
if (parent != null) {
if (parent is TextNode) {
return parent;
}
return forwardNearestTextNode(parent);
}
return null;
}
// find the last text node
static TextNode? findLastTextNode(Node node) {
final children = node.children.toList(growable: false).reversed;
for (final child in children) {
if (child.children.isNotEmpty) {
final result = findLastTextNode(child);
if (result != null) {
return result;
}
}
if (child is TextNode) {
return child;
}
}
if (node is TextNode) {
return node;
}
return null;
}
}

View file

@ -1,351 +0,0 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
Iterable<ThemeExtension<dynamic>> get lightPluginStyleExtension => [
HeadingPluginStyle.light,
CheckboxPluginStyle.light,
NumberListPluginStyle.light,
QuotedTextPluginStyle.light,
BulletedListPluginStyle.light,
];
Iterable<ThemeExtension<dynamic>> get darkPluginStyleExtension => [
HeadingPluginStyle.dark,
CheckboxPluginStyle.dark,
NumberListPluginStyle.dark,
QuotedTextPluginStyle.dark,
BulletedListPluginStyle.dark,
];
typedef TextStyleCustomizer = TextStyle Function(
EditorState editorState,
TextNode textNode,
);
typedef PaddingCustomizer = EdgeInsets Function(
EditorState editorState,
TextNode textNode,
);
typedef IconCustomizer = Widget Function(
EditorState editorState,
TextNode textNode,
);
class HeadingPluginStyle extends ThemeExtension<HeadingPluginStyle> {
const HeadingPluginStyle({
required this.textStyle,
required this.padding,
});
final TextStyleCustomizer textStyle;
final PaddingCustomizer padding;
@override
HeadingPluginStyle copyWith({
TextStyleCustomizer? textStyle,
PaddingCustomizer? padding,
}) {
return HeadingPluginStyle(
textStyle: textStyle ?? this.textStyle,
padding: padding ?? this.padding,
);
}
@override
ThemeExtension<HeadingPluginStyle> lerp(
ThemeExtension<HeadingPluginStyle>? other,
double t,
) {
if (other is! HeadingPluginStyle) {
return this;
}
return HeadingPluginStyle(
textStyle: other.textStyle,
padding: other.padding,
);
}
static final light = HeadingPluginStyle(
padding: (_, __) => const EdgeInsets.symmetric(vertical: 8.0),
textStyle: (editorState, textNode) {
final headingToFontSize = {
'h1': 32.0,
'h2': 28.0,
'h3': 24.0,
'h4': 18.0,
'h5': 18.0,
'h6': 18.0,
};
final fontSize = headingToFontSize[textNode.attributes.heading] ?? 18.0;
return TextStyle(
fontSize: fontSize,
fontWeight: FontWeight.bold,
);
},
);
static final dark = light;
}
class CheckboxPluginStyle extends ThemeExtension<CheckboxPluginStyle> {
const CheckboxPluginStyle({
required this.textStyle,
required this.padding,
required this.icon,
});
final TextStyleCustomizer textStyle;
final PaddingCustomizer padding;
final IconCustomizer icon;
@override
CheckboxPluginStyle copyWith({
TextStyleCustomizer? textStyle,
PaddingCustomizer? padding,
IconCustomizer? icon,
}) {
return CheckboxPluginStyle(
textStyle: textStyle ?? this.textStyle,
padding: padding ?? this.padding,
icon: icon ?? this.icon,
);
}
@override
ThemeExtension<CheckboxPluginStyle> lerp(
ThemeExtension<CheckboxPluginStyle>? other,
double t,
) {
if (other is! CheckboxPluginStyle) {
return this;
}
return CheckboxPluginStyle(
textStyle: other.textStyle,
padding: other.padding,
icon: other.icon,
);
}
static final light = CheckboxPluginStyle(
padding: (_, __) => const EdgeInsets.symmetric(vertical: 8.0),
textStyle: (editorState, textNode) {
final isCheck = textNode.attributes.check;
return TextStyle(
decoration: isCheck ? TextDecoration.lineThrough : null,
color: isCheck ? Colors.grey.shade400 : null,
);
},
icon: (editorState, textNode) {
final isCheck = textNode.attributes.check;
const iconSize = Size.square(20.0);
const iconPadding = EdgeInsets.only(right: 5.0);
return EditorSvg(
width: iconSize.width,
height: iconSize.height,
padding: iconPadding,
name: isCheck ? 'check' : 'uncheck',
);
},
);
static final dark = light;
}
class BulletedListPluginStyle extends ThemeExtension<BulletedListPluginStyle> {
const BulletedListPluginStyle({
required this.textStyle,
required this.padding,
required this.icon,
});
final TextStyleCustomizer textStyle;
final PaddingCustomizer padding;
final IconCustomizer icon;
@override
BulletedListPluginStyle copyWith({
TextStyleCustomizer? textStyle,
PaddingCustomizer? padding,
IconCustomizer? icon,
}) {
return BulletedListPluginStyle(
textStyle: textStyle ?? this.textStyle,
padding: padding ?? this.padding,
icon: icon ?? this.icon,
);
}
@override
ThemeExtension<BulletedListPluginStyle> lerp(
ThemeExtension<BulletedListPluginStyle>? other,
double t,
) {
if (other is! BulletedListPluginStyle) {
return this;
}
return BulletedListPluginStyle(
textStyle: other.textStyle,
padding: other.padding,
icon: other.icon,
);
}
static final light = BulletedListPluginStyle(
padding: (_, __) => const EdgeInsets.symmetric(vertical: 8.0),
textStyle: (_, __) => const TextStyle(),
icon: (_, __) {
const iconSize = Size.square(20.0);
const iconPadding = EdgeInsets.only(right: 5.0);
return EditorSvg(
width: iconSize.width,
height: iconSize.height,
padding: iconPadding,
color: Colors.black,
name: 'point',
);
},
);
static final dark = light.copyWith(
icon: (_, __) {
const iconSize = Size.square(20.0);
const iconPadding = EdgeInsets.only(right: 5.0);
return EditorSvg(
width: iconSize.width,
height: iconSize.height,
padding: iconPadding,
color: Colors.white,
name: 'point',
);
},
);
}
class NumberListPluginStyle extends ThemeExtension<NumberListPluginStyle> {
const NumberListPluginStyle({
required this.textStyle,
required this.padding,
required this.icon,
});
final TextStyleCustomizer textStyle;
final PaddingCustomizer padding;
final IconCustomizer icon;
@override
NumberListPluginStyle copyWith({
TextStyleCustomizer? textStyle,
PaddingCustomizer? padding,
IconCustomizer? icon,
}) {
return NumberListPluginStyle(
textStyle: textStyle ?? this.textStyle,
padding: padding ?? this.padding,
icon: icon ?? this.icon,
);
}
@override
ThemeExtension<NumberListPluginStyle> lerp(
ThemeExtension<NumberListPluginStyle>? other,
double t,
) {
if (other is! NumberListPluginStyle) {
return this;
}
return NumberListPluginStyle(
textStyle: other.textStyle,
padding: other.padding,
icon: other.icon,
);
}
static final light = NumberListPluginStyle(
padding: (_, __) => const EdgeInsets.symmetric(vertical: 8.0),
textStyle: (_, __) => const TextStyle(),
icon: (_, textNode) {
const iconPadding = EdgeInsets.only(left: 5.0, right: 5.0);
return Container(
padding: iconPadding,
child: Text(
'${textNode.attributes.number.toString()}.',
style: const TextStyle(
fontSize: 16,
color: Colors.black,
),
),
);
},
);
static final dark = light.copyWith(
icon: (editorState, textNode) {
const iconPadding = EdgeInsets.only(left: 5.0, right: 5.0);
return Container(
padding: iconPadding,
child: Text(
'${textNode.attributes.number.toString()}.',
style: const TextStyle(
fontSize: 16,
color: Colors.white,
),
),
);
},
);
}
class QuotedTextPluginStyle extends ThemeExtension<QuotedTextPluginStyle> {
const QuotedTextPluginStyle({
required this.textStyle,
required this.padding,
required this.icon,
});
final TextStyleCustomizer textStyle;
final PaddingCustomizer padding;
final IconCustomizer icon;
@override
QuotedTextPluginStyle copyWith({
TextStyleCustomizer? textStyle,
PaddingCustomizer? padding,
IconCustomizer? icon,
}) {
return QuotedTextPluginStyle(
textStyle: textStyle ?? this.textStyle,
padding: padding ?? this.padding,
icon: icon ?? this.icon,
);
}
@override
ThemeExtension<QuotedTextPluginStyle> lerp(
ThemeExtension<QuotedTextPluginStyle>? other,
double t,
) {
if (other is! QuotedTextPluginStyle) {
return this;
}
return QuotedTextPluginStyle(
textStyle: other.textStyle,
padding: other.padding,
icon: other.icon,
);
}
static final light = QuotedTextPluginStyle(
padding: (_, __) => const EdgeInsets.symmetric(vertical: 8.0),
textStyle: (_, __) => const TextStyle(),
icon: (_, __) {
const iconSize = Size.square(20.0);
const iconPadding = EdgeInsets.only(right: 5.0);
return EditorSvg(
width: iconSize.width,
padding: iconPadding,
name: 'quote',
);
},
);
static final dark = light;
}

View file

@ -1,28 +1,21 @@
import 'package:appflowy_editor/src/service/context_menu/context_menu.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/copy_paste_handler.dart';
final builtInContextMenuItems = [
[
// cut
ContextMenuItem(
name: 'Cut',
onPressed: (editorState) {
cutEventHandler(editorState, null);
},
onPressed: (editorState) {},
),
// copy
ContextMenuItem(
name: 'Copy',
onPressed: (editorState) {
copyEventHandler(editorState, null);
},
onPressed: (editorState) {},
),
// Paste
ContextMenuItem(
name: 'Paste',
onPressed: (editorState) {
pasteEventHandler(editorState, null);
},
onPressed: (editorState) {},
),
],
];

View file

@ -70,147 +70,3 @@ bool insertNodeAfterSelection(
editorState.apply(transaction);
return true;
}
void formatText(EditorState editorState) {
throw UnimplementedError();
}
void formatHeading(EditorState editorState, String heading) {
throw UnimplementedError();
}
void formatQuote(EditorState editorState) {
throw UnimplementedError();
}
void formatCheckbox(EditorState editorState, bool check) {
throw UnimplementedError();
}
void formatBulletedList(EditorState editorState) {
throw UnimplementedError();
}
bool formatBold(EditorState editorState) {
return formatRichTextPartialStyle(editorState, BuiltInAttributeKey.bold);
}
bool formatItalic(EditorState editorState) {
return formatRichTextPartialStyle(editorState, BuiltInAttributeKey.italic);
}
bool formatUnderline(EditorState editorState) {
return formatRichTextPartialStyle(editorState, BuiltInAttributeKey.underline);
}
bool formatStrikethrough(EditorState editorState) {
return formatRichTextPartialStyle(
editorState,
BuiltInAttributeKey.strikethrough,
);
}
bool formatEmbedCode(EditorState editorState) {
return formatRichTextPartialStyle(editorState, BuiltInAttributeKey.code);
}
bool formatHighlight(EditorState editorState, String colorHex) {
bool value = _allSatisfyInSelection(
editorState,
BuiltInAttributeKey.highlightColor,
colorHex,
);
return formatRichTextPartialStyle(
editorState,
BuiltInAttributeKey.highlightColor,
customValue: value ? '0x00000000' : colorHex,
);
}
bool formatRichTextPartialStyle(
EditorState editorState,
String styleKey, {
Object? customValue,
}) {
Attributes attributes = {
styleKey: customValue ??
!_allSatisfyInSelection(
editorState,
styleKey,
customValue ?? true,
),
};
return formatRichTextStyle(editorState, attributes);
}
bool _allSatisfyInSelection(
EditorState editorState,
String styleKey,
dynamic matchValue,
) {
final selection = editorState.service.selectionService.currentSelection.value;
final nodes = editorState.service.selectionService.currentSelectedNodes;
final textNodes = nodes.whereType<TextNode>().toList(growable: false);
if (selection == null || textNodes.isEmpty) {
return false;
}
return textNodes.allSatisfyInSelection(selection, styleKey, (value) {
return value == matchValue;
});
}
bool formatRichTextStyle(EditorState editorState, Attributes attributes) {
var selection = editorState.service.selectionService.currentSelection.value;
var nodes = editorState.service.selectionService.currentSelectedNodes;
if (selection == null) {
return false;
}
nodes = selection.isBackward ? nodes : nodes.reversed.toList(growable: false);
selection = selection.isBackward ? selection : selection.reversed;
var textNodes = nodes.whereType<TextNode>().toList();
if (textNodes.isEmpty) {
return false;
}
final transaction = editorState.transaction;
// 1. All nodes are text nodes.
// 2. The first node is not TextNode.
// 3. The last node is not TextNode.
if (nodes.length == textNodes.length && textNodes.length == 1) {
transaction.formatText(
textNodes.first,
selection.start.offset,
selection.end.offset - selection.start.offset,
attributes,
);
} else {
for (var i = 0; i < textNodes.length; i++) {
final textNode = textNodes[i];
var index = 0;
var length = textNode.toPlainText().length;
if (i == 0 && textNode == nodes.first) {
index = selection.start.offset;
length = textNode.toPlainText().length - selection.start.offset;
} else if (i == textNodes.length - 1 && textNode == nodes.last) {
length = selection.end.offset;
}
transaction.formatText(
textNode,
index,
length,
attributes,
);
}
}
editorState.apply(transaction);
return true;
}

View file

@ -1,39 +0,0 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/service/shortcut_event/shortcut_event_handler.dart';
import 'package:flutter/material.dart';
ShortcutEventHandler toggleCheckbox = (editorState, event) {
final selection = editorState.service.selectionService.currentSelection.value;
final nodes = editorState.service.selectionService.currentSelectedNodes;
final checkboxTextNodes = nodes
.where(
(element) =>
element is TextNode &&
element.subtype == BuiltInAttributeKey.checkbox,
)
.toList(growable: false);
if (selection == null || checkboxTextNodes.isEmpty) {
return KeyEventResult.ignored;
}
bool isAllCheckboxesChecked = checkboxTextNodes
.every((node) => node.attributes[BuiltInAttributeKey.checkbox] == true);
final transaction = editorState.transaction;
transaction.afterSelection = selection;
if (isAllCheckboxesChecked) {
//if all the checkboxes are checked, then make all of the checkboxes unchecked
for (final node in checkboxTextNodes) {
transaction.updateNode(node, {BuiltInAttributeKey.checkbox: false});
}
} else {
//If any one of the checkboxes is unchecked then make all checkboxes checked
for (final node in checkboxTextNodes) {
transaction.updateNode(node, {BuiltInAttributeKey.checkbox: true});
}
}
editorState.apply(transaction);
return KeyEventResult.handled;
};

Some files were not shown because too many files have changed in this diff Show more