mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
[editor] Add save keyboard shortcut.
This commit is contained in:
parent
b0df64138f
commit
7b1e5aaba2
@ -58,8 +58,4 @@ class EditorWdiget extends StatelessWidget {
|
|||||||
Future<String> _onImageSelection(File file) {
|
Future<String> _onImageSelection(File file) {
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
void save() {
|
|
||||||
final deltaJson = controller.document.toDelta().toJson();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -49,4 +49,4 @@ SPEC CHECKSUMS:
|
|||||||
|
|
||||||
PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c
|
PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c
|
||||||
|
|
||||||
COCOAPODS: 1.10.1
|
COCOAPODS: 1.9.3
|
||||||
|
@ -38,8 +38,7 @@ class EditorController extends ChangeNotifier {
|
|||||||
);
|
);
|
||||||
|
|
||||||
Style getSelectionStyle() =>
|
Style getSelectionStyle() =>
|
||||||
document.collectStyle(selection.start, selection.end - selection.start)
|
document.collectStyle(selection.start, selection.end - selection.start)..mergeAll(toggledStyle);
|
||||||
..mergeAll(toggledStyle);
|
|
||||||
|
|
||||||
bool get hasUndo => document.hasUndo;
|
bool get hasUndo => document.hasUndo;
|
||||||
|
|
||||||
@ -59,6 +58,12 @@ class EditorController extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> save() async {
|
||||||
|
document.toDelta().toJson();
|
||||||
|
// TODO: vedon - Save document to database
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
document.close();
|
document.close();
|
||||||
@ -75,9 +80,7 @@ class EditorController extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void formatText(int index, int length, Attribute? attribute) {
|
void formatText(int index, int length, Attribute? attribute) {
|
||||||
if (length == 0 &&
|
if (length == 0 && attribute!.isInline && attribute.key != Attribute.link.key) {
|
||||||
attribute!.isInline &&
|
|
||||||
attribute.key != Attribute.link.key) {
|
|
||||||
toggledStyle = toggledStyle.put(attribute);
|
toggledStyle = toggledStyle.put(attribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,24 +95,16 @@ class EditorController extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void replaceText(
|
void replaceText(int index, int length, Object? data, TextSelection? textSelection) {
|
||||||
int index, int length, Object? data, TextSelection? textSelection) {
|
|
||||||
assert(data is String || data is Embeddable);
|
assert(data is String || data is Embeddable);
|
||||||
|
|
||||||
Delta? delta;
|
Delta? delta;
|
||||||
if (length > 0 || data is! String || data.isNotEmpty) {
|
if (length > 0 || data is! String || data.isNotEmpty) {
|
||||||
delta = document.replace(index, length, data);
|
delta = document.replace(index, length, data);
|
||||||
var shouldRetainDelta = toggledStyle.isNotEmpty &&
|
var shouldRetainDelta = toggledStyle.isNotEmpty && delta.isNotEmpty && delta.length <= 2 && delta.last.isInsert;
|
||||||
delta.isNotEmpty &&
|
if (shouldRetainDelta && toggledStyle.isNotEmpty && delta.length == 2 && delta.last.data == '\n') {
|
||||||
delta.length <= 2 &&
|
|
||||||
delta.last.isInsert;
|
|
||||||
if (shouldRetainDelta &&
|
|
||||||
toggledStyle.isNotEmpty &&
|
|
||||||
delta.length == 2 &&
|
|
||||||
delta.last.data == '\n') {
|
|
||||||
// if all attributes are inline, shouldRetainDelta should be false
|
// if all attributes are inline, shouldRetainDelta should be false
|
||||||
final anyAttributeNotInline =
|
final anyAttributeNotInline = toggledStyle.values.any((attr) => !attr.isInline);
|
||||||
toggledStyle.values.any((attr) => !attr.isInline);
|
|
||||||
shouldRetainDelta &= anyAttributeNotInline;
|
shouldRetainDelta &= anyAttributeNotInline;
|
||||||
}
|
}
|
||||||
if (shouldRetainDelta) {
|
if (shouldRetainDelta) {
|
||||||
@ -151,8 +146,7 @@ class EditorController extends ChangeNotifier {
|
|||||||
|
|
||||||
textSelection = selection.copyWith(
|
textSelection = selection.copyWith(
|
||||||
baseOffset: delta.transformPosition(selection.baseOffset, force: false),
|
baseOffset: delta.transformPosition(selection.baseOffset, force: false),
|
||||||
extentOffset:
|
extentOffset: delta.transformPosition(selection.extentOffset, force: false),
|
||||||
delta.transformPosition(selection.extentOffset, force: false),
|
|
||||||
);
|
);
|
||||||
if (selection != textSelection) {
|
if (selection != textSelection) {
|
||||||
_updateSelection(textSelection, source);
|
_updateSelection(textSelection, source);
|
||||||
|
@ -8,6 +8,7 @@ enum InputShortcut {
|
|||||||
COPY,
|
COPY,
|
||||||
PASTE,
|
PASTE,
|
||||||
SELECT_ALL,
|
SELECT_ALL,
|
||||||
|
SAVE,
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef CursorMoveCallback = void Function(
|
typedef CursorMoveCallback = void Function(
|
||||||
@ -46,6 +47,7 @@ class KeyboardListener {
|
|||||||
LogicalKeyboardKey.keyC,
|
LogicalKeyboardKey.keyC,
|
||||||
LogicalKeyboardKey.keyV,
|
LogicalKeyboardKey.keyV,
|
||||||
LogicalKeyboardKey.keyX,
|
LogicalKeyboardKey.keyX,
|
||||||
|
LogicalKeyboardKey.keyS,
|
||||||
LogicalKeyboardKey.delete,
|
LogicalKeyboardKey.delete,
|
||||||
LogicalKeyboardKey.backspace,
|
LogicalKeyboardKey.backspace,
|
||||||
};
|
};
|
||||||
@ -78,6 +80,7 @@ class KeyboardListener {
|
|||||||
LogicalKeyboardKey.keyC: InputShortcut.COPY,
|
LogicalKeyboardKey.keyC: InputShortcut.COPY,
|
||||||
LogicalKeyboardKey.keyV: InputShortcut.PASTE,
|
LogicalKeyboardKey.keyV: InputShortcut.PASTE,
|
||||||
LogicalKeyboardKey.keyA: InputShortcut.SELECT_ALL,
|
LogicalKeyboardKey.keyA: InputShortcut.SELECT_ALL,
|
||||||
|
LogicalKeyboardKey.keyS: InputShortcut.SAVE,
|
||||||
};
|
};
|
||||||
|
|
||||||
bool handleRawKeyEvent(RawKeyEvent event) {
|
bool handleRawKeyEvent(RawKeyEvent event) {
|
||||||
@ -89,8 +92,7 @@ class KeyboardListener {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final keysPressed =
|
final keysPressed = LogicalKeyboardKey.collapseSynonyms(RawKeyboard.instance.keysPressed);
|
||||||
LogicalKeyboardKey.collapseSynonyms(RawKeyboard.instance.keysPressed);
|
|
||||||
final key = event.logicalKey;
|
final key = event.logicalKey;
|
||||||
final isMacOS = event.data is RawKeyEventDataMacOs;
|
final isMacOS = event.data is RawKeyEventDataMacOs;
|
||||||
final modifierKeys = isMacOS ? _osxModifierKeys : _winModifierKeys;
|
final modifierKeys = isMacOS ? _osxModifierKeys : _winModifierKeys;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
@ -59,8 +60,7 @@ class RawEditor extends StatefulWidget {
|
|||||||
this.embedBuilder,
|
this.embedBuilder,
|
||||||
) : assert(maxHeight == null || maxHeight > 0, 'maxHeight cannot be null'),
|
) : assert(maxHeight == null || maxHeight > 0, 'maxHeight cannot be null'),
|
||||||
assert(minHeight == null || minHeight >= 0, 'minHeight cannot be null'),
|
assert(minHeight == null || minHeight >= 0, 'minHeight cannot be null'),
|
||||||
assert(
|
assert(maxHeight == null || minHeight == null || maxHeight >= minHeight),
|
||||||
maxHeight == null || minHeight == null || maxHeight >= minHeight),
|
|
||||||
showCursor = showCursor ?? true,
|
showCursor = showCursor ?? true,
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
@ -111,10 +111,7 @@ abstract class EditorState extends State<RawEditor> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _RawEditorState extends EditorState
|
class _RawEditorState extends EditorState
|
||||||
with
|
with AutomaticKeepAliveClientMixin<RawEditor>, WidgetsBindingObserver, TickerProviderStateMixin<RawEditor>
|
||||||
AutomaticKeepAliveClientMixin<RawEditor>,
|
|
||||||
WidgetsBindingObserver,
|
|
||||||
TickerProviderStateMixin<RawEditor>
|
|
||||||
implements TextSelectionDelegate, TextInputClient {
|
implements TextSelectionDelegate, TextInputClient {
|
||||||
final GlobalKey _editorKey = GlobalKey();
|
final GlobalKey _editorKey = GlobalKey();
|
||||||
final List<TextEditingValue> _sentRemoteValues = [];
|
final List<TextEditingValue> _sentRemoteValues = [];
|
||||||
@ -132,8 +129,7 @@ class _RawEditorState extends EditorState
|
|||||||
bool _didAutoFocus = false;
|
bool _didAutoFocus = false;
|
||||||
bool _keyboardVisible = false;
|
bool _keyboardVisible = false;
|
||||||
DefaultStyles? _styles;
|
DefaultStyles? _styles;
|
||||||
final ClipboardStatusNotifier? _clipboardStatus =
|
final ClipboardStatusNotifier? _clipboardStatus = kIsWeb ? null : ClipboardStatusNotifier();
|
||||||
kIsWeb ? null : ClipboardStatusNotifier();
|
|
||||||
final LayerLink _toolbarLayerLink = LayerLink();
|
final LayerLink _toolbarLayerLink = LayerLink();
|
||||||
final LayerLink _startHandleLayerLink = LayerLink();
|
final LayerLink _startHandleLayerLink = LayerLink();
|
||||||
final LayerLink _endHandleLayerLink = LayerLink();
|
final LayerLink _endHandleLayerLink = LayerLink();
|
||||||
@ -181,78 +177,57 @@ class _RawEditorState extends EditorState
|
|||||||
downKey = key == LogicalKeyboardKey.arrowDown;
|
downKey = key == LogicalKeyboardKey.arrowDown;
|
||||||
|
|
||||||
if ((rightKey || leftKey) && !(rightKey && leftKey)) {
|
if ((rightKey || leftKey) && !(rightKey && leftKey)) {
|
||||||
newSelection = _jumpToBeginOrEndOfWord(newSelection, wordModifier,
|
newSelection =
|
||||||
leftKey, rightKey, plainText, lineModifier, shift);
|
_jumpToBeginOrEndOfWord(newSelection, wordModifier, leftKey, rightKey, plainText, lineModifier, shift);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downKey || upKey) {
|
if (downKey || upKey) {
|
||||||
newSelection = _handleMovingCursorVertically(
|
newSelection = _handleMovingCursorVertically(upKey, downKey, shift, selection, newSelection, plainText);
|
||||||
upKey, downKey, shift, selection, newSelection, plainText);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!shift) {
|
if (!shift) {
|
||||||
newSelection =
|
newSelection = _placeCollapsedSelection(selection, newSelection, leftKey, rightKey);
|
||||||
_placeCollapsedSelection(selection, newSelection, leftKey, rightKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
widget.controller.updateSelection(newSelection, ChangeSource.LOCAL);
|
widget.controller.updateSelection(newSelection, ChangeSource.LOCAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextSelection _placeCollapsedSelection(TextSelection selection,
|
TextSelection _placeCollapsedSelection(
|
||||||
TextSelection newSelection, bool leftKey, bool rightKey) {
|
TextSelection selection, TextSelection newSelection, bool leftKey, bool rightKey) {
|
||||||
var newOffset = newSelection.extentOffset;
|
var newOffset = newSelection.extentOffset;
|
||||||
if (!selection.isCollapsed) {
|
if (!selection.isCollapsed) {
|
||||||
if (leftKey) {
|
if (leftKey) {
|
||||||
newOffset = newSelection.baseOffset < newSelection.extentOffset
|
newOffset =
|
||||||
? newSelection.baseOffset
|
newSelection.baseOffset < newSelection.extentOffset ? newSelection.baseOffset : newSelection.extentOffset;
|
||||||
: newSelection.extentOffset;
|
|
||||||
} else if (rightKey) {
|
} else if (rightKey) {
|
||||||
newOffset = newSelection.baseOffset > newSelection.extentOffset
|
newOffset =
|
||||||
? newSelection.baseOffset
|
newSelection.baseOffset > newSelection.extentOffset ? newSelection.baseOffset : newSelection.extentOffset;
|
||||||
: newSelection.extentOffset;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return TextSelection.fromPosition(TextPosition(offset: newOffset));
|
return TextSelection.fromPosition(TextPosition(offset: newOffset));
|
||||||
}
|
}
|
||||||
|
|
||||||
TextSelection _handleMovingCursorVertically(
|
TextSelection _handleMovingCursorVertically(
|
||||||
bool upKey,
|
bool upKey, bool downKey, bool shift, TextSelection selection, TextSelection newSelection, String plainText) {
|
||||||
bool downKey,
|
final originPosition = TextPosition(offset: upKey ? selection.baseOffset : selection.extentOffset);
|
||||||
bool shift,
|
|
||||||
TextSelection selection,
|
|
||||||
TextSelection newSelection,
|
|
||||||
String plainText) {
|
|
||||||
final originPosition = TextPosition(
|
|
||||||
offset: upKey ? selection.baseOffset : selection.extentOffset);
|
|
||||||
|
|
||||||
final child = getRenderEditor()!.childAtPosition(originPosition);
|
final child = getRenderEditor()!.childAtPosition(originPosition);
|
||||||
final localPosition = TextPosition(
|
final localPosition = TextPosition(offset: originPosition.offset - child.container.documentOffset);
|
||||||
offset: originPosition.offset - child.container.documentOffset);
|
|
||||||
|
|
||||||
var position = upKey
|
var position = upKey ? child.getPositionAbove(localPosition) : child.getPositionBelow(localPosition);
|
||||||
? child.getPositionAbove(localPosition)
|
|
||||||
: child.getPositionBelow(localPosition);
|
|
||||||
|
|
||||||
if (position == null) {
|
if (position == null) {
|
||||||
final sibling = upKey
|
final sibling = upKey ? getRenderEditor()!.childBefore(child) : getRenderEditor()!.childAfter(child);
|
||||||
? getRenderEditor()!.childBefore(child)
|
|
||||||
: getRenderEditor()!.childAfter(child);
|
|
||||||
if (sibling == null) {
|
if (sibling == null) {
|
||||||
position = TextPosition(offset: upKey ? 0 : plainText.length - 1);
|
position = TextPosition(offset: upKey ? 0 : plainText.length - 1);
|
||||||
} else {
|
} else {
|
||||||
final finalOffset = Offset(
|
final finalOffset = Offset(child.getOffsetForCaret(localPosition).dx,
|
||||||
child.getOffsetForCaret(localPosition).dx,
|
sibling.getOffsetForCaret(TextPosition(offset: upKey ? sibling.container.length - 1 : 0)).dy);
|
||||||
sibling
|
|
||||||
.getOffsetForCaret(TextPosition(
|
|
||||||
offset: upKey ? sibling.container.length - 1 : 0))
|
|
||||||
.dy);
|
|
||||||
final siblingPosition = sibling.getPositionForOffset(finalOffset);
|
final siblingPosition = sibling.getPositionForOffset(finalOffset);
|
||||||
position = TextPosition(
|
position = TextPosition(offset: sibling.container.documentOffset + siblingPosition.offset);
|
||||||
offset: sibling.container.documentOffset + siblingPosition.offset);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
position = TextPosition(
|
position = TextPosition(offset: child.container.documentOffset + position.offset);
|
||||||
offset: child.container.documentOffset + position.offset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (position.offset == newSelection.extentOffset) {
|
if (position.offset == newSelection.extentOffset) {
|
||||||
@ -275,47 +250,33 @@ class _RawEditorState extends EditorState
|
|||||||
return newSelection;
|
return newSelection;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextSelection _jumpToBeginOrEndOfWord(
|
TextSelection _jumpToBeginOrEndOfWord(TextSelection newSelection, bool wordModifier, bool leftKey, bool rightKey,
|
||||||
TextSelection newSelection,
|
String plainText, bool lineModifier, bool shift) {
|
||||||
bool wordModifier,
|
|
||||||
bool leftKey,
|
|
||||||
bool rightKey,
|
|
||||||
String plainText,
|
|
||||||
bool lineModifier,
|
|
||||||
bool shift) {
|
|
||||||
if (wordModifier) {
|
if (wordModifier) {
|
||||||
if (leftKey) {
|
if (leftKey) {
|
||||||
final textSelection = getRenderEditor()!.selectWordAtPosition(
|
final textSelection = getRenderEditor()!.selectWordAtPosition(
|
||||||
TextPosition(
|
TextPosition(offset: _previousCharacter(newSelection.extentOffset, plainText, false)));
|
||||||
offset: _previousCharacter(
|
|
||||||
newSelection.extentOffset, plainText, false)));
|
|
||||||
return newSelection.copyWith(extentOffset: textSelection.baseOffset);
|
return newSelection.copyWith(extentOffset: textSelection.baseOffset);
|
||||||
}
|
}
|
||||||
final textSelection = getRenderEditor()!.selectWordAtPosition(
|
final textSelection = getRenderEditor()!
|
||||||
TextPosition(
|
.selectWordAtPosition(TextPosition(offset: _nextCharacter(newSelection.extentOffset, plainText, false)));
|
||||||
offset:
|
|
||||||
_nextCharacter(newSelection.extentOffset, plainText, false)));
|
|
||||||
return newSelection.copyWith(extentOffset: textSelection.extentOffset);
|
return newSelection.copyWith(extentOffset: textSelection.extentOffset);
|
||||||
} else if (lineModifier) {
|
} else if (lineModifier) {
|
||||||
if (leftKey) {
|
if (leftKey) {
|
||||||
final textSelection = getRenderEditor()!.selectLineAtPosition(
|
final textSelection = getRenderEditor()!.selectLineAtPosition(
|
||||||
TextPosition(
|
TextPosition(offset: _previousCharacter(newSelection.extentOffset, plainText, false)));
|
||||||
offset: _previousCharacter(
|
|
||||||
newSelection.extentOffset, plainText, false)));
|
|
||||||
return newSelection.copyWith(extentOffset: textSelection.baseOffset);
|
return newSelection.copyWith(extentOffset: textSelection.baseOffset);
|
||||||
}
|
}
|
||||||
final startPoint = newSelection.extentOffset;
|
final startPoint = newSelection.extentOffset;
|
||||||
if (startPoint < plainText.length) {
|
if (startPoint < plainText.length) {
|
||||||
final textSelection = getRenderEditor()!
|
final textSelection = getRenderEditor()!.selectLineAtPosition(TextPosition(offset: startPoint));
|
||||||
.selectLineAtPosition(TextPosition(offset: startPoint));
|
|
||||||
return newSelection.copyWith(extentOffset: textSelection.extentOffset);
|
return newSelection.copyWith(extentOffset: textSelection.extentOffset);
|
||||||
}
|
}
|
||||||
return newSelection;
|
return newSelection;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rightKey && newSelection.extentOffset < plainText.length) {
|
if (rightKey && newSelection.extentOffset < plainText.length) {
|
||||||
final nextExtent =
|
final nextExtent = _nextCharacter(newSelection.extentOffset, plainText, true);
|
||||||
_nextCharacter(newSelection.extentOffset, plainText, true);
|
|
||||||
final distance = nextExtent - newSelection.extentOffset;
|
final distance = nextExtent - newSelection.extentOffset;
|
||||||
newSelection = newSelection.copyWith(extentOffset: nextExtent);
|
newSelection = newSelection.copyWith(extentOffset: nextExtent);
|
||||||
if (shift) {
|
if (shift) {
|
||||||
@ -325,8 +286,7 @@ class _RawEditorState extends EditorState
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (leftKey && newSelection.extentOffset > 0) {
|
if (leftKey && newSelection.extentOffset > 0) {
|
||||||
final previousExtent =
|
final previousExtent = _previousCharacter(newSelection.extentOffset, plainText, true);
|
||||||
_previousCharacter(newSelection.extentOffset, plainText, true);
|
|
||||||
final distance = newSelection.extentOffset - previousExtent;
|
final distance = newSelection.extentOffset - previousExtent;
|
||||||
newSelection = newSelection.copyWith(extentOffset: previousExtent);
|
newSelection = newSelection.copyWith(extentOffset: previousExtent);
|
||||||
if (shift) {
|
if (shift) {
|
||||||
@ -366,9 +326,7 @@ class _RawEditorState extends EditorState
|
|||||||
var count = 0;
|
var count = 0;
|
||||||
int? lastNonWhitespace;
|
int? lastNonWhitespace;
|
||||||
for (final currentString in string.characters) {
|
for (final currentString in string.characters) {
|
||||||
if (!includeWhitespace &&
|
if (!includeWhitespace && !WHITE_SPACE.contains(currentString.characters.first.toString().codeUnitAt(0))) {
|
||||||
!WHITE_SPACE.contains(
|
|
||||||
currentString.characters.first.toString().codeUnitAt(0))) {
|
|
||||||
lastNonWhitespace = count;
|
lastNonWhitespace = count;
|
||||||
}
|
}
|
||||||
if (count + currentString.length >= index) {
|
if (count + currentString.length >= index) {
|
||||||
@ -379,8 +337,7 @@ class _RawEditorState extends EditorState
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get hasConnection =>
|
bool get hasConnection => _textInputConnection != null && _textInputConnection!.attached;
|
||||||
_textInputConnection != null && _textInputConnection!.attached;
|
|
||||||
|
|
||||||
void openConnectionIfNeeded() {
|
void openConnectionIfNeeded() {
|
||||||
if (!shouldCreateInputConnection) {
|
if (!shouldCreateInputConnection) {
|
||||||
@ -431,8 +388,7 @@ class _RawEditorState extends EditorState
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final shouldRemember =
|
final shouldRemember = textEditingValue.text != _lastKnownRemoteTextEditingValue!.text;
|
||||||
textEditingValue.text != _lastKnownRemoteTextEditingValue!.text;
|
|
||||||
_lastKnownRemoteTextEditingValue = actualValue;
|
_lastKnownRemoteTextEditingValue = actualValue;
|
||||||
_textInputConnection!.setEditingState(actualValue);
|
_textInputConnection!.setEditingState(actualValue);
|
||||||
if (shouldRemember) {
|
if (shouldRemember) {
|
||||||
@ -441,8 +397,7 @@ class _RawEditorState extends EditorState
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TextEditingValue? get currentTextEditingValue =>
|
TextEditingValue? get currentTextEditingValue => _lastKnownRemoteTextEditingValue;
|
||||||
_lastKnownRemoteTextEditingValue;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AutofillScope? get currentAutofillScope => null;
|
AutofillScope? get currentAutofillScope => null;
|
||||||
@ -474,8 +429,7 @@ class _RawEditorState extends EditorState
|
|||||||
final text = value.text;
|
final text = value.text;
|
||||||
final cursorPosition = value.selection.extentOffset;
|
final cursorPosition = value.selection.extentOffset;
|
||||||
final diff = getDiff(oldText, text, cursorPosition);
|
final diff = getDiff(oldText, text, cursorPosition);
|
||||||
widget.controller.replaceText(
|
widget.controller.replaceText(diff.start, diff.deleted.length, diff.inserted, value.selection);
|
||||||
diff.start, diff.deleted.length, diff.inserted, value.selection);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -525,11 +479,8 @@ class _RawEditorState extends EditorState
|
|||||||
super.build(context);
|
super.build(context);
|
||||||
|
|
||||||
var _doc = widget.controller.document;
|
var _doc = widget.controller.document;
|
||||||
if (_doc.isEmpty() &&
|
if (_doc.isEmpty() && !widget.focusNode.hasFocus && widget.placeholder != null) {
|
||||||
!widget.focusNode.hasFocus &&
|
_doc = Document.fromJson(jsonDecode('[{"attributes":{"placeholder":true},"insert":"${widget.placeholder}\\n"}]'));
|
||||||
widget.placeholder != null) {
|
|
||||||
_doc = Document.fromJson(jsonDecode(
|
|
||||||
'[{"attributes":{"placeholder":true},"insert":"${widget.placeholder}\\n"}]'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget child = CompositedTransformTarget(
|
Widget child = CompositedTransformTarget(
|
||||||
@ -552,8 +503,7 @@ class _RawEditorState extends EditorState
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (widget.scrollable) {
|
if (widget.scrollable) {
|
||||||
final baselinePadding =
|
final baselinePadding = EdgeInsets.only(top: _styles!.paragraph!.verticalSpacing.item1);
|
||||||
EdgeInsets.only(top: _styles!.paragraph!.verticalSpacing.item1);
|
|
||||||
child = BaselineProxy(
|
child = BaselineProxy(
|
||||||
textStyle: _styles!.paragraph!.style,
|
textStyle: _styles!.paragraph!.style,
|
||||||
padding: baselinePadding,
|
padding: baselinePadding,
|
||||||
@ -584,8 +534,7 @@ class _RawEditorState extends EditorState
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleSelectionChanged(
|
void _handleSelectionChanged(TextSelection selection, SelectionChangedCause cause) {
|
||||||
TextSelection selection, SelectionChangedCause cause) {
|
|
||||||
widget.controller.updateSelection(selection, ChangeSource.LOCAL);
|
widget.controller.updateSelection(selection, ChangeSource.LOCAL);
|
||||||
|
|
||||||
_selectionOverlay?.handlesVisible = _shouldShowSelectionHandles();
|
_selectionOverlay?.handlesVisible = _shouldShowSelectionHandles();
|
||||||
@ -614,9 +563,7 @@ class _RawEditorState extends EditorState
|
|||||||
_styles,
|
_styles,
|
||||||
widget.enableInteractiveSelection,
|
widget.enableInteractiveSelection,
|
||||||
_hasFocus,
|
_hasFocus,
|
||||||
attrs.containsKey(Attribute.codeBlock.key)
|
attrs.containsKey(Attribute.codeBlock.key) ? const EdgeInsets.all(16) : null,
|
||||||
? const EdgeInsets.all(16)
|
|
||||||
: null,
|
|
||||||
widget.embedBuilder,
|
widget.embedBuilder,
|
||||||
_cursorController,
|
_cursorController,
|
||||||
indentLevelCounts);
|
indentLevelCounts);
|
||||||
@ -628,8 +575,7 @@ class _RawEditorState extends EditorState
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
EditableTextLine _getEditableTextLineFromNode(
|
EditableTextLine _getEditableTextLineFromNode(Line node, BuildContext context) {
|
||||||
Line node, BuildContext context) {
|
|
||||||
final textLine = TextLine(
|
final textLine = TextLine(
|
||||||
line: node,
|
line: node,
|
||||||
textDirection: _textDirection,
|
textDirection: _textDirection,
|
||||||
@ -652,8 +598,7 @@ class _RawEditorState extends EditorState
|
|||||||
return editableTextLine;
|
return editableTextLine;
|
||||||
}
|
}
|
||||||
|
|
||||||
Tuple2<double, double> _getVerticalSpacingForLine(
|
Tuple2<double, double> _getVerticalSpacingForLine(Line line, DefaultStyles? defaultStyles) {
|
||||||
Line line, DefaultStyles? defaultStyles) {
|
|
||||||
final attrs = line.style.attributes;
|
final attrs = line.style.attributes;
|
||||||
if (attrs.containsKey(Attribute.header.key)) {
|
if (attrs.containsKey(Attribute.header.key)) {
|
||||||
final int? level = attrs[Attribute.header.key]!.value;
|
final int? level = attrs[Attribute.header.key]!.value;
|
||||||
@ -678,8 +623,7 @@ class _RawEditorState extends EditorState
|
|||||||
return defaultStyles!.paragraph!.verticalSpacing;
|
return defaultStyles!.paragraph!.verticalSpacing;
|
||||||
}
|
}
|
||||||
|
|
||||||
Tuple2<double, double> _getVerticalSpacingForBlock(
|
Tuple2<double, double> _getVerticalSpacingForBlock(Block node, DefaultStyles? defaultStyles) {
|
||||||
Block node, DefaultStyles? defaultStyles) {
|
|
||||||
final attrs = node.style.attributes;
|
final attrs = node.style.attributes;
|
||||||
if (attrs.containsKey(Attribute.quoteBlock.key)) {
|
if (attrs.containsKey(Attribute.quoteBlock.key)) {
|
||||||
return defaultStyles!.quote!.verticalSpacing;
|
return defaultStyles!.quote!.verticalSpacing;
|
||||||
@ -722,8 +666,7 @@ class _RawEditorState extends EditorState
|
|||||||
} else {
|
} else {
|
||||||
_keyboardVisibilityController = KeyboardVisibilityController();
|
_keyboardVisibilityController = KeyboardVisibilityController();
|
||||||
_keyboardVisible = _keyboardVisibilityController!.isVisible;
|
_keyboardVisible = _keyboardVisibilityController!.isVisible;
|
||||||
_keyboardVisibilitySubscription =
|
_keyboardVisibilitySubscription = _keyboardVisibilityController?.onChange.listen((visible) {
|
||||||
_keyboardVisibilityController?.onChange.listen((visible) {
|
|
||||||
_keyboardVisible = visible;
|
_keyboardVisible = visible;
|
||||||
if (visible) {
|
if (visible) {
|
||||||
_onChangeTextEditingValue();
|
_onChangeTextEditingValue();
|
||||||
@ -746,9 +689,7 @@ class _RawEditorState extends EditorState
|
|||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
final parentStyles = EditorStyles.getStyles(context, true);
|
final parentStyles = EditorStyles.getStyles(context, true);
|
||||||
final defaultStyles = DefaultStyles.getInstance(context);
|
final defaultStyles = DefaultStyles.getInstance(context);
|
||||||
_styles = (parentStyles != null)
|
_styles = (parentStyles != null) ? defaultStyles.merge(parentStyles) : defaultStyles;
|
||||||
? defaultStyles.merge(parentStyles)
|
|
||||||
: defaultStyles;
|
|
||||||
|
|
||||||
if (widget.customStyles != null) {
|
if (widget.customStyles != null) {
|
||||||
_styles = _styles!.merge(widget.customStyles!);
|
_styles = _styles!.merge(widget.customStyles!);
|
||||||
@ -808,8 +749,7 @@ class _RawEditorState extends EditorState
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _shouldShowSelectionHandles() {
|
bool _shouldShowSelectionHandles() {
|
||||||
return widget.showSelectionHandles &&
|
return widget.showSelectionHandles && !widget.controller.selection.isCollapsed;
|
||||||
!widget.controller.selection.isCollapsed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleDelete(bool forward) {
|
void handleDelete(bool forward) {
|
||||||
@ -820,8 +760,7 @@ class _RawEditorState extends EditorState
|
|||||||
var textAfter = selection.textAfter(plainText);
|
var textAfter = selection.textAfter(plainText);
|
||||||
if (selection.isCollapsed) {
|
if (selection.isCollapsed) {
|
||||||
if (!forward && textBefore.isNotEmpty) {
|
if (!forward && textBefore.isNotEmpty) {
|
||||||
final characterBoundary =
|
final characterBoundary = _previousCharacter(textBefore.length, textBefore, true);
|
||||||
_previousCharacter(textBefore.length, textBefore, true);
|
|
||||||
textBefore = textBefore.substring(0, characterBoundary);
|
textBefore = textBefore.substring(0, characterBoundary);
|
||||||
cursorPosition = characterBoundary;
|
cursorPosition = characterBoundary;
|
||||||
}
|
}
|
||||||
@ -844,10 +783,16 @@ class _RawEditorState extends EditorState
|
|||||||
Future<void> handleShortcut(InputShortcut? shortcut) async {
|
Future<void> handleShortcut(InputShortcut? shortcut) async {
|
||||||
final selection = widget.controller.selection;
|
final selection = widget.controller.selection;
|
||||||
final plainText = textEditingValue.text;
|
final plainText = textEditingValue.text;
|
||||||
|
if (shortcut == InputShortcut.SAVE) {
|
||||||
|
bool saved = await widget.controller.save();
|
||||||
|
if (!saved) {
|
||||||
|
log('Unabled to save document.');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (shortcut == InputShortcut.COPY) {
|
if (shortcut == InputShortcut.COPY) {
|
||||||
if (!selection.isCollapsed) {
|
if (!selection.isCollapsed) {
|
||||||
await Clipboard.setData(
|
await Clipboard.setData(ClipboardData(text: selection.textInside(plainText)));
|
||||||
ClipboardData(text: selection.textInside(plainText)));
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -864,8 +809,7 @@ class _RawEditorState extends EditorState
|
|||||||
);
|
);
|
||||||
|
|
||||||
textEditingValue = TextEditingValue(
|
textEditingValue = TextEditingValue(
|
||||||
text:
|
text: selection.textBefore(plainText) + selection.textAfter(plainText),
|
||||||
selection.textBefore(plainText) + selection.textAfter(plainText),
|
|
||||||
selection: TextSelection.collapsed(offset: selection.start),
|
selection: TextSelection.collapsed(offset: selection.start),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -883,8 +827,7 @@ class _RawEditorState extends EditorState
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (shortcut == InputShortcut.SELECT_ALL &&
|
if (shortcut == InputShortcut.SELECT_ALL && widget.enableInteractiveSelection) {
|
||||||
widget.enableInteractiveSelection) {
|
|
||||||
widget.controller.updateSelection(
|
widget.controller.updateSelection(
|
||||||
selection.copyWith(
|
selection.copyWith(
|
||||||
baseOffset: 0,
|
baseOffset: 0,
|
||||||
@ -938,16 +881,14 @@ class _RawEditorState extends EditorState
|
|||||||
void _onChangeTextEditingValue() {
|
void _onChangeTextEditingValue() {
|
||||||
_showCaretOnScreen();
|
_showCaretOnScreen();
|
||||||
updateRemoteValueIfNeeded();
|
updateRemoteValueIfNeeded();
|
||||||
_cursorController.startOrStopCursorTimerIfNeeded(
|
_cursorController.startOrStopCursorTimerIfNeeded(_hasFocus, widget.controller.selection);
|
||||||
_hasFocus, widget.controller.selection);
|
|
||||||
if (hasConnection) {
|
if (hasConnection) {
|
||||||
_cursorController
|
_cursorController
|
||||||
..stopCursorTimer(resetCharTicks: false)
|
..stopCursorTimer(resetCharTicks: false)
|
||||||
..startCursorTimer();
|
..startCursorTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
SchedulerBinding.instance!.addPostFrameCallback(
|
SchedulerBinding.instance!.addPostFrameCallback((_) => _updateOrDisposeSelectionOverlayIfNeeded());
|
||||||
(_) => _updateOrDisposeSelectionOverlayIfNeeded());
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
// Use widget.controller.value in build()
|
// Use widget.controller.value in build()
|
||||||
@ -990,8 +931,7 @@ class _RawEditorState extends EditorState
|
|||||||
|
|
||||||
void _handleFocusChanged() {
|
void _handleFocusChanged() {
|
||||||
openOrCloseConnection();
|
openOrCloseConnection();
|
||||||
_cursorController.startOrStopCursorTimerIfNeeded(
|
_cursorController.startOrStopCursorTimerIfNeeded(_hasFocus, widget.controller.selection);
|
||||||
_hasFocus, widget.controller.selection);
|
|
||||||
_updateOrDisposeSelectionOverlayIfNeeded();
|
_updateOrDisposeSelectionOverlayIfNeeded();
|
||||||
if (_hasFocus) {
|
if (_hasFocus) {
|
||||||
WidgetsBinding.instance!.addObserver(this);
|
WidgetsBinding.instance!.addObserver(this);
|
||||||
@ -1022,8 +962,7 @@ class _RawEditorState extends EditorState
|
|||||||
_showCaretOnScreenScheduled = false;
|
_showCaretOnScreenScheduled = false;
|
||||||
|
|
||||||
final viewport = RenderAbstractViewport.of(getRenderEditor())!;
|
final viewport = RenderAbstractViewport.of(getRenderEditor())!;
|
||||||
final editorOffset = getRenderEditor()!
|
final editorOffset = getRenderEditor()!.localToGlobal(const Offset(0, 0), ancestor: viewport);
|
||||||
.localToGlobal(const Offset(0, 0), ancestor: viewport);
|
|
||||||
final offsetInViewport = _scrollController!.offset + editorOffset.dy;
|
final offsetInViewport = _scrollController!.offset + editorOffset.dy;
|
||||||
|
|
||||||
final offset = getRenderEditor()!.getOffsetToRevealCursor(
|
final offset = getRenderEditor()!.getOffsetToRevealCursor(
|
||||||
@ -1106,8 +1045,7 @@ class _RawEditorState extends EditorState
|
|||||||
final value = textEditingValue;
|
final value = textEditingValue;
|
||||||
final data = await Clipboard.getData(Clipboard.kTextPlain);
|
final data = await Clipboard.getData(Clipboard.kTextPlain);
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
final length =
|
final length = textEditingValue.selection.end - textEditingValue.selection.start;
|
||||||
textEditingValue.selection.end - textEditingValue.selection.start;
|
|
||||||
widget.controller.replaceText(
|
widget.controller.replaceText(
|
||||||
value.selection.start,
|
value.selection.start,
|
||||||
length,
|
length,
|
||||||
@ -1116,9 +1054,7 @@ class _RawEditorState extends EditorState
|
|||||||
);
|
);
|
||||||
// move cursor to the end of pasted text selection
|
// move cursor to the end of pasted text selection
|
||||||
widget.controller.updateSelection(
|
widget.controller.updateSelection(
|
||||||
TextSelection.collapsed(
|
TextSelection.collapsed(offset: value.selection.start + data.text!.length), ChangeSource.LOCAL);
|
||||||
offset: value.selection.start + data.text!.length),
|
|
||||||
ChangeSource.LOCAL);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1128,8 +1064,7 @@ class _RawEditorState extends EditorState
|
|||||||
if (data == null) {
|
if (data == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return textEditingValue.text.length - value.text.length ==
|
return textEditingValue.text.length - value.text.length == data.text!.length;
|
||||||
data.text!.length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1162,8 +1097,7 @@ class _RawEditorState extends EditorState
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void userUpdateTextEditingValue(
|
void userUpdateTextEditingValue(TextEditingValue value, SelectionChangedCause cause) {
|
||||||
TextEditingValue value, SelectionChangedCause cause) {
|
|
||||||
// TODO: implement userUpdateTextEditingValue
|
// TODO: implement userUpdateTextEditingValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1213,8 +1147,7 @@ class _Editor extends MultiChildRenderObjectWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void updateRenderObject(
|
void updateRenderObject(BuildContext context, covariant RenderEditor renderObject) {
|
||||||
BuildContext context, covariant RenderEditor renderObject) {
|
|
||||||
renderObject
|
renderObject
|
||||||
..document = document
|
..document = document
|
||||||
..container = document.root
|
..container = document.root
|
||||||
|
@ -14,7 +14,7 @@ packages:
|
|||||||
name: analyzer
|
name: analyzer
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.7.2"
|
version: "1.7.1"
|
||||||
animations:
|
animations:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -35,7 +35,7 @@ packages:
|
|||||||
name: async
|
name: async
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.7.0"
|
version: "2.6.1"
|
||||||
bloc:
|
bloc:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -119,7 +119,7 @@ packages:
|
|||||||
name: charcode
|
name: charcode
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.2.0"
|
||||||
checked_yaml:
|
checked_yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -456,7 +456,7 @@ packages:
|
|||||||
name: meta
|
name: meta
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.7.0"
|
version: "1.3.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -701,7 +701,7 @@ packages:
|
|||||||
name: test_api
|
name: test_api
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.1"
|
version: "0.3.0"
|
||||||
textstyle_extensions:
|
textstyle_extensions:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
Loading…
Reference in New Issue
Block a user