|
@@ -23,98 +23,6 @@ class TextNodeBuilder extends NodeWidgetBuilder {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-extension on Attributes {
|
|
|
- TextStyle toTextStyle() {
|
|
|
- return TextStyle(
|
|
|
- color: this['color'] != null ? Colors.red : Colors.black,
|
|
|
- fontSize: this['font-size'] != null ? 30 : 15,
|
|
|
- );
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-TextSpan _textInsertToTextSpan(TextInsert textInsert) {
|
|
|
- FontWeight? fontWeight;
|
|
|
- FontStyle? fontStyle;
|
|
|
- TextDecoration? decoration;
|
|
|
- GestureRecognizer? gestureRecognizer;
|
|
|
- Color? color;
|
|
|
- final attributes = textInsert.attributes;
|
|
|
- if (attributes?['bold'] == true) {
|
|
|
- fontWeight = FontWeight.bold;
|
|
|
- }
|
|
|
- if (attributes?['italic'] == true) {
|
|
|
- fontStyle = FontStyle.italic;
|
|
|
- }
|
|
|
- if (attributes?["underline"] == true) {
|
|
|
- decoration = TextDecoration.underline;
|
|
|
- }
|
|
|
- if (attributes?["href"] is String) {
|
|
|
- color = const Color.fromARGB(255, 55, 120, 245);
|
|
|
- decoration = TextDecoration.underline;
|
|
|
- gestureRecognizer = TapGestureRecognizer()
|
|
|
- ..onTap = () {
|
|
|
- // TODO: open the link
|
|
|
- };
|
|
|
- }
|
|
|
- return TextSpan(
|
|
|
- text: textInsert.content,
|
|
|
- style: TextStyle(
|
|
|
- fontWeight: fontWeight,
|
|
|
- fontStyle: fontStyle,
|
|
|
- decoration: decoration,
|
|
|
- color: color,
|
|
|
- ),
|
|
|
- recognizer: gestureRecognizer);
|
|
|
-}
|
|
|
-
|
|
|
-extension on TextNode {
|
|
|
- List<TextSpan> toTextSpans() {
|
|
|
- final result = <TextSpan>[];
|
|
|
-
|
|
|
- for (final op in delta.operations) {
|
|
|
- if (op is TextInsert) {
|
|
|
- result.add(_textInsertToTextSpan(op));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return result;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-TextSelection? _globalSelectionToLocal(Node node, Selection? globalSel) {
|
|
|
- if (globalSel == null) {
|
|
|
- return null;
|
|
|
- }
|
|
|
- final nodePath = node.path;
|
|
|
-
|
|
|
- if (!pathEquals(nodePath, globalSel.start.path)) {
|
|
|
- return null;
|
|
|
- }
|
|
|
- if (globalSel.isCollapsed()) {
|
|
|
- return TextSelection(
|
|
|
- baseOffset: globalSel.start.offset, extentOffset: globalSel.end.offset);
|
|
|
- } else {
|
|
|
- if (pathEquals(globalSel.start.path, globalSel.end.path)) {
|
|
|
- return TextSelection(
|
|
|
- baseOffset: globalSel.start.offset,
|
|
|
- extentOffset: globalSel.end.offset);
|
|
|
- }
|
|
|
- }
|
|
|
- return null;
|
|
|
-}
|
|
|
-
|
|
|
-Selection? _localSelectionToGlobal(Node node, TextSelection? sel) {
|
|
|
- if (sel == null) {
|
|
|
- return null;
|
|
|
- }
|
|
|
- final nodePath = node.path;
|
|
|
-
|
|
|
- return Selection(
|
|
|
- start: Position(path: nodePath, offset: sel.baseOffset),
|
|
|
- end: Position(path: nodePath, offset: sel.extentOffset),
|
|
|
- );
|
|
|
-}
|
|
|
-
|
|
|
class _TextNodeWidget extends StatefulWidget {
|
|
|
final Node node;
|
|
|
final EditorState editorState;
|
|
@@ -144,10 +52,6 @@ class __TextNodeWidgetState extends State<_TextNodeWidget>
|
|
|
TextNode get node => widget.node as TextNode;
|
|
|
EditorState get editorState => widget.editorState;
|
|
|
|
|
|
- TextEditingValue get textEditingValue => TextEditingValue(
|
|
|
- text: node.toRawString(),
|
|
|
- );
|
|
|
-
|
|
|
TextInputConnection? _textInputConnection;
|
|
|
|
|
|
_backDeleteTextAtSelection(TextSelection? sel) {
|
|
@@ -190,7 +94,7 @@ class __TextNodeWidgetState extends State<_TextNodeWidget>
|
|
|
|
|
|
_setEditingStateFromGlobal() {
|
|
|
_textInputConnection?.setEditingState(TextEditingValue(
|
|
|
- text: _textContentOfDelta(node.delta),
|
|
|
+ text: node.toString(),
|
|
|
selection: _globalSelectionToLocal(node, editorState.cursorSelection) ??
|
|
|
const TextSelection.collapsed(offset: 0)));
|
|
|
}
|
|
@@ -236,9 +140,12 @@ class __TextNodeWidgetState extends State<_TextNodeWidget>
|
|
|
_localSelectionToGlobal(node, selection);
|
|
|
_textInputConnection
|
|
|
?..show()
|
|
|
- ..setEditingState(TextEditingValue(
|
|
|
- text: _textContentOfDelta(node.delta),
|
|
|
- selection: selection));
|
|
|
+ ..setEditingState(
|
|
|
+ TextEditingValue(
|
|
|
+ text: node.toString(),
|
|
|
+ selection: selection,
|
|
|
+ ),
|
|
|
+ );
|
|
|
}),
|
|
|
),
|
|
|
),
|
|
@@ -271,7 +178,7 @@ class __TextNodeWidgetState extends State<_TextNodeWidget>
|
|
|
@override
|
|
|
// TODO: implement currentTextEditingValue
|
|
|
TextEditingValue? get currentTextEditingValue => TextEditingValue(
|
|
|
- text: _textContentOfDelta(node.delta),
|
|
|
+ text: node.toString(),
|
|
|
selection: _globalSelectionToLocal(node, editorState.cursorSelection) ??
|
|
|
const TextSelection.collapsed(offset: 0));
|
|
|
|
|
@@ -336,67 +243,98 @@ class __TextNodeWidgetState extends State<_TextNodeWidget>
|
|
|
extension on TextNode {
|
|
|
List<TextSpan> toTextSpans() => delta.operations
|
|
|
.whereType<TextInsert>()
|
|
|
- .map((op) => _textInsertToTextSpan(op))
|
|
|
+ .map((op) => op.toTextSpan())
|
|
|
.toList();
|
|
|
-
|
|
|
- String toRawString() => delta.operations
|
|
|
- .whereType<TextInsert>()
|
|
|
- .map((op) => op.content)
|
|
|
- .toString();
|
|
|
}
|
|
|
|
|
|
-TextSpan _textInsertToTextSpan(TextInsert textInsert) {
|
|
|
- FontWeight? fontWeight;
|
|
|
- FontStyle? fontStyle;
|
|
|
- TextDecoration? decoration;
|
|
|
- GestureRecognizer? gestureRecognizer;
|
|
|
- Color? color;
|
|
|
- Color highLightColor = Colors.transparent;
|
|
|
- double fontSize = 16.0;
|
|
|
- final attributes = textInsert.attributes;
|
|
|
- if (attributes?['bold'] == true) {
|
|
|
- fontWeight = FontWeight.bold;
|
|
|
- }
|
|
|
- if (attributes?['italic'] == true) {
|
|
|
- fontStyle = FontStyle.italic;
|
|
|
- }
|
|
|
- if (attributes?['underline'] == true) {
|
|
|
- decoration = TextDecoration.underline;
|
|
|
- }
|
|
|
- if (attributes?['strikethrough'] == true) {
|
|
|
- decoration = TextDecoration.lineThrough;
|
|
|
+extension on TextInsert {
|
|
|
+ TextSpan toTextSpan() {
|
|
|
+ FontWeight? fontWeight;
|
|
|
+ FontStyle? fontStyle;
|
|
|
+ TextDecoration? decoration;
|
|
|
+ GestureRecognizer? gestureRecognizer;
|
|
|
+ Color? color;
|
|
|
+ Color highLightColor = Colors.transparent;
|
|
|
+ double fontSize = 16.0;
|
|
|
+ final attributes = this.attributes;
|
|
|
+ if (attributes?['bold'] == true) {
|
|
|
+ fontWeight = FontWeight.bold;
|
|
|
+ }
|
|
|
+ if (attributes?['italic'] == true) {
|
|
|
+ fontStyle = FontStyle.italic;
|
|
|
+ }
|
|
|
+ if (attributes?['underline'] == true) {
|
|
|
+ decoration = TextDecoration.underline;
|
|
|
+ }
|
|
|
+ if (attributes?['strikethrough'] == true) {
|
|
|
+ decoration = TextDecoration.lineThrough;
|
|
|
+ }
|
|
|
+ if (attributes?['highlight'] is String) {
|
|
|
+ highLightColor = Color(int.parse(attributes!['highlight']));
|
|
|
+ }
|
|
|
+ if (attributes?['href'] is String) {
|
|
|
+ color = const Color.fromARGB(255, 55, 120, 245);
|
|
|
+ decoration = TextDecoration.underline;
|
|
|
+ gestureRecognizer = TapGestureRecognizer()
|
|
|
+ ..onTap = () {
|
|
|
+ launchUrlString(attributes?['href']);
|
|
|
+ };
|
|
|
+ }
|
|
|
+ final heading = attributes?['heading'] as String?;
|
|
|
+ if (heading != null) {
|
|
|
+ // TODO: make it better
|
|
|
+ if (heading == 'h1') {
|
|
|
+ fontSize = 30.0;
|
|
|
+ } else if (heading == 'h2') {
|
|
|
+ fontSize = 20.0;
|
|
|
+ }
|
|
|
+ fontWeight = FontWeight.bold;
|
|
|
+ }
|
|
|
+ return TextSpan(
|
|
|
+ text: content,
|
|
|
+ style: TextStyle(
|
|
|
+ fontWeight: fontWeight,
|
|
|
+ fontStyle: fontStyle,
|
|
|
+ decoration: decoration,
|
|
|
+ color: color,
|
|
|
+ fontSize: fontSize,
|
|
|
+ backgroundColor: highLightColor,
|
|
|
+ ),
|
|
|
+ recognizer: gestureRecognizer,
|
|
|
+ );
|
|
|
}
|
|
|
- if (attributes?['highlight'] is String) {
|
|
|
- highLightColor = Color(int.parse(attributes!['highlight']));
|
|
|
+}
|
|
|
+
|
|
|
+TextSelection? _globalSelectionToLocal(Node node, Selection? globalSel) {
|
|
|
+ if (globalSel == null) {
|
|
|
+ return null;
|
|
|
}
|
|
|
- if (attributes?['href'] is String) {
|
|
|
- color = const Color.fromARGB(255, 55, 120, 245);
|
|
|
- decoration = TextDecoration.underline;
|
|
|
- gestureRecognizer = TapGestureRecognizer()
|
|
|
- ..onTap = () {
|
|
|
- launchUrlString(attributes?['href']);
|
|
|
- };
|
|
|
+ final nodePath = node.path;
|
|
|
+
|
|
|
+ if (!pathEquals(nodePath, globalSel.start.path)) {
|
|
|
+ return null;
|
|
|
}
|
|
|
- final heading = attributes?['heading'] as String?;
|
|
|
- if (heading != null) {
|
|
|
- // TODO: make it better
|
|
|
- if (heading == 'h1') {
|
|
|
- fontSize = 30.0;
|
|
|
- } else if (heading == 'h2') {
|
|
|
- fontSize = 20.0;
|
|
|
+ if (globalSel.isCollapsed()) {
|
|
|
+ return TextSelection(
|
|
|
+ baseOffset: globalSel.start.offset, extentOffset: globalSel.end.offset);
|
|
|
+ } else {
|
|
|
+ if (pathEquals(globalSel.start.path, globalSel.end.path)) {
|
|
|
+ return TextSelection(
|
|
|
+ baseOffset: globalSel.start.offset,
|
|
|
+ extentOffset: globalSel.end.offset);
|
|
|
}
|
|
|
- fontWeight = FontWeight.bold;
|
|
|
}
|
|
|
- return TextSpan(
|
|
|
- text: textInsert.content,
|
|
|
- style: TextStyle(
|
|
|
- fontWeight: fontWeight,
|
|
|
- fontStyle: fontStyle,
|
|
|
- decoration: decoration,
|
|
|
- color: color,
|
|
|
- fontSize: fontSize,
|
|
|
- backgroundColor: highLightColor,
|
|
|
- ),
|
|
|
- recognizer: gestureRecognizer,
|
|
|
+ return null;
|
|
|
+}
|
|
|
+
|
|
|
+Selection? _localSelectionToGlobal(Node node, TextSelection? sel) {
|
|
|
+ if (sel == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ final nodePath = node.path;
|
|
|
+
|
|
|
+ return Selection(
|
|
|
+ start: Position(path: nodePath, offset: sel.baseOffset),
|
|
|
+ end: Position(path: nodePath, offset: sel.extentOffset),
|
|
|
);
|
|
|
}
|