diff --git a/example/lib/screens/quill/my_quill_editor.dart b/example/lib/screens/quill/my_quill_editor.dart index 254057d7..d50f75d8 100644 --- a/example/lib/screens/quill/my_quill_editor.dart +++ b/example/lib/screens/quill/my_quill_editor.dart @@ -30,6 +30,7 @@ class MyQuillEditor extends StatelessWidget { @override Widget build(BuildContext context) { + final defaultTextStyle = DefaultTextStyle.of(context); return QuillEditor( scrollController: scrollController, focusNode: focusNode, @@ -43,27 +44,21 @@ class MyQuillEditor extends StatelessWidget { useTextColorForDot: true, ), ), - customStyles: const DefaultStyles( + customStyles: DefaultStyles( h1: DefaultTextBlockStyle( - TextStyle( + defaultTextStyle.style.copyWith( fontSize: 32, height: 1.15, fontWeight: FontWeight.w300, ), - VerticalSpacing(16, 0), - VerticalSpacing(0, 0), + const VerticalSpacing(16, 0), + const VerticalSpacing(0, 0), null, ), - sizeSmall: TextStyle(fontSize: 9), - subscript: TextStyle( - fontFeatures: [FontFeature.subscripts()], - ), - superscript: TextStyle( - fontFeatures: [FontFeature.superscripts()], - ), + sizeSmall: defaultTextStyle.style.copyWith(fontSize: 9), ), scrollable: true, - placeholder: 'Start writting your notes...', + placeholder: 'Start writing your notes...', padding: const EdgeInsets.all(16), onImagePaste: (imageBytes) async { if (isWeb()) { diff --git a/lib/src/models/documents/document.dart b/lib/src/models/documents/document.dart index 99a72e7a..b46e6707 100644 --- a/lib/src/models/documents/document.dart +++ b/lib/src/models/documents/document.dart @@ -177,26 +177,46 @@ class Document { /// Only attributes applied to all characters within this range are /// included in the result. + /// Special case of no-selection at start of empty line: gets inline style(s) from preceding non-empty line. Style collectStyle(int index, int len) { - final res = queryChild(index); - Style rangeStyle; + var res = queryChild(index); if (len > 0) { return (res.node as Line).collectStyle(res.offset, len); } + // if (res.offset == 0) { - return rangeStyle = (res.node as Line).collectStyle(res.offset, len); + final current = (res.node as Line).collectStyle(0, 0); + // + while ((res.node as Line).length == 1 && index > 0) { + res = queryChild(--index); + } + // + final style = (res.node as Line).collectStyle(res.offset, 0); + final remove = {}; + for (final attr in style.attributes.values) { + if (!Attribute.inlineKeys.contains(attr.key)) { + if (!current.containsKey(attr.key)) { + remove.add(attr); + } + } + } + if (remove.isNotEmpty) { + return style.removeAll(remove); + } + return style; } - rangeStyle = (res.node as Line).collectStyle(res.offset - 1, len); - final linkAttribute = rangeStyle.attributes[Attribute.link.key]; + // + final style = (res.node as Line).collectStyle(res.offset - 1, 0); + final linkAttribute = style.attributes[Attribute.link.key]; if ((linkAttribute != null) && (linkAttribute.value != (res.node as Line) .collectStyle(res.offset, len) .attributes[Attribute.link.key] ?.value)) { - return rangeStyle.removeAll({linkAttribute}); + return style.removeAll({linkAttribute}); } - return rangeStyle; + return style; } /// Returns all styles and Embed for each node within selection diff --git a/lib/src/widgets/raw_editor/raw_editor_state.dart b/lib/src/widgets/raw_editor/raw_editor_state.dart index 1f2b7ff4..ccd91fb5 100644 --- a/lib/src/widgets/raw_editor/raw_editor_state.dart +++ b/lib/src/widgets/raw_editor/raw_editor_state.dart @@ -875,6 +875,14 @@ class QuillRawEditorState extends EditorState ..formatSelection(attribute) // Remove the added newline. ..replaceText(controller.selection.baseOffset + 1, 1, '', null); + // + final style = + controller.document.collectStyle(controller.selection.baseOffset, 0); + if (style.isNotEmpty) { + for (final attr in style.values) { + controller.formatSelection(attr); + } + } } void _handleSelectionChanged( diff --git a/test/utils/document_test.dart b/test/utils/document_test.dart new file mode 100644 index 00000000..c672e74d --- /dev/null +++ b/test/utils/document_test.dart @@ -0,0 +1,85 @@ +import 'package:flutter_quill/flutter_quill.dart'; +import 'package:flutter_quill/quill_delta.dart'; +import 'package:test/test.dart'; + +void main() { + group('collectStyle', () { + /// Enter key inserts newline as plain text without inline styles. + /// collectStyle needs to retrieve style of preceding line + test('Simulate double enter key at end', () { + final delta = Delta() + ..insert('data\n') + ..insert('second\n', {'bold': true}) + ..insert('\n\nplain\n'); + final document = Document.fromDelta(delta); + // + expect(document.getPlainText(0, document.length), + 'data\nsecond\n\n\nplain\n'); + expect(document.length, 20); + // + expect('data\n', document.getPlainText(0, 5)); + for (var index = 0; index < 5; index++) { + expect(const Style(), document.collectStyle(index, 0)); + } + // + expect('second\n', document.getPlainText(5, 7)); + for (var index = 5; index < 12; index++) { + expect(const Style.attr({'bold': Attribute.bold}), + document.collectStyle(index, 0)); + } + // + expect('\n\n', document.getPlainText(12, 2)); + for (var index = 12; index < 14; index++) { + expect(const Style.attr({'bold': Attribute.bold}), + document.collectStyle(index, 0)); + } + // + for (var index = 14; index < document.length; index++) { + expect(const Style(), document.collectStyle(index, 0)); + } + }); + + test('No selection', () { + final delta = Delta() + ..insert('plain\n') + ..insert('bold\n', {'bold': true}) + ..insert('italic\n', {'italic': true}); + final document = Document.fromDelta(delta); + // + expect( + document.getPlainText(0, document.length), 'plain\nbold\nitalic\n'); + expect(document.length, 18); + // + for (var index = 0; index < 6; index++) { + expect(const Style(), document.collectStyle(index, 0)); + } + // + for (var index = 6; index < 11; index++) { + expect(const Style.attr({'bold': Attribute.bold}), + document.collectStyle(index, 0)); + } + // + for (var index = 11; index < document.length; index++) { + expect(const Style.attr({'italic': Attribute.italic}), + document.collectStyle(index, 0)); + } + }); + + test('Selection', () { + final delta = Delta() + ..insert('data\n') + ..insert('second\n', {'bold': true}); + final document = Document.fromDelta(delta); + // + expect(const Style(), document.collectStyle(0, 4)); + expect(const Style(), document.collectStyle(1, 3)); + // + expect(const Style.attr({'bold': Attribute.bold}), + document.collectStyle(5, 3)); + expect(const Style.attr({'bold': Attribute.bold}), + document.collectStyle(8, 3)); + // + expect(const Style(), document.collectStyle(3, 3)); + }); + }); +}