From 2a92287c6448edf412f7e1c5a24813bf51a53510 Mon Sep 17 00:00:00 2001 From: Xin Yao Date: Wed, 4 Aug 2021 23:47:25 -0700 Subject: [PATCH] Support inline image --- example/assets/sample_data.json | 514 ------------------------- lib/src/models/documents/document.dart | 32 +- lib/src/widgets/controller.dart | 5 +- lib/src/widgets/text_line.dart | 26 +- 4 files changed, 31 insertions(+), 546 deletions(-) diff --git a/example/assets/sample_data.json b/example/assets/sample_data.json index bf7b4881..ac8509e8 100644 --- a/example/assets/sample_data.json +++ b/example/assets/sample_data.json @@ -11,520 +11,6 @@ { "insert":"Flutter Quill" }, - { - "attributes":{ - "header":1 - }, - "insert":"\n" - }, - { - "insert": { - "video": "https://www.youtube.com/watch?v=V4hgdKhIqtc&list=PLbhaS_83B97s78HsDTtplRTEhcFsqSqIK&index=1" - } - }, - { - "insert": { - "video": "https://user-images.githubusercontent.com/122956/126238875-22e42501-ad41-4266-b1d6-3f89b5e3b79b.mp4" - } - }, - { - "insert":"\nRich text editor for Flutter" - }, - { - "attributes":{ - "header":2 - }, - "insert":"\n" - }, - { - "insert":"Quill component for Flutter" - }, - { - "attributes":{ - "header":3 - }, - "insert":"\n" - }, - { - "insert":"This " - }, - { - "attributes":{ - "italic":true, - "background":"transparent" - }, - "insert":"library" - }, - { - "insert":" supports " - }, - { - "attributes":{ - "bold":true, - "background":"#ebd6ff" - }, - "insert":"mobile" - }, - { - "insert":" platform " - }, - { - "attributes":{ - "underline":true, - "bold":true, - "color":"#e60000" - }, - "insert":"only" - }, - { - "attributes":{ - "color":"rgba(0, 0, 0, 0.847)" - }, - "insert":" and " - }, - { - "attributes":{ - "strike":true, - "color":"black" - }, - "insert":"web" - }, - { - "insert":" is not supported.\nYou are welcome to use " - }, - { - "attributes":{ - "link":"https://bulletjournal.us/home/index.html" - }, - "insert":"Bullet Journal" - }, - { - "insert":":\nTrack personal and group journals (ToDo, Note, Ledger) from multiple views with timely reminders" - }, - { - "attributes":{ - "list":"ordered" - }, - "insert":"\n" - }, - { - "insert":"Share your tasks and notes with teammates, and see changes as they happen in real-time, across all devices" - }, - { - "attributes":{ - "list":"ordered" - }, - "insert":"\n" - }, - { - "insert":"Check out what you and your teammates are working on each day" - }, - { - "attributes":{ - "list":"ordered" - }, - "insert":"\n" - }, - { - "insert":"\nSplitting bills with friends can never be easier." - }, - { - "attributes":{ - "list":"bullet" - }, - "insert":"\n" - }, - { - "insert":"Start creating a group and invite your friends to join." - }, - { - "attributes":{ - "list":"bullet" - }, - "insert":"\n" - }, - { - "insert":"Create a BuJo of Ledger type to see expense or balance summary." - }, - { - "attributes":{ - "list":"bullet" - }, - "insert":"\n" - }, - { - "insert":"\nAttach one or multiple labels to tasks, notes or transactions. Later you can track them just using the label(s)." - }, - { - "attributes":{ - "blockquote":true - }, - "insert":"\n" - }, - { - "insert":"\nvar BuJo = 'Bullet' + 'Journal'" - }, - { - "attributes":{ - "code-block":true - }, - "insert":"\n" - }, - { - "insert":"\nStart tracking in your browser" - }, - { - "attributes":{ - "indent":1 - }, - "insert":"\n" - }, - { - "insert":"Stop the timer on your phone" - }, - { - "attributes":{ - "indent":1 - }, - "insert":"\n" - }, - { - "insert":"All your time entries are synced" - }, - { - "attributes":{ - "indent":2 - }, - "insert":"\n" - }, - { - "insert":"between the phone apps" - }, - { - "attributes":{ - "indent":2 - }, - "insert":"\n" - }, - { - "insert":"and the website." - }, - { - "attributes":{ - "indent":3 - }, - "insert":"\n" - }, - { - "insert":"\n" - }, - { - "insert":"\nCenter Align" - }, - { - "attributes":{ - "align":"center" - }, - "insert":"\n" - }, - { - "insert":"Right Align" - }, - { - "attributes":{ - "align":"right" - }, - "insert":"\n" - }, - { - "insert":"Justify Align" - }, - { - "attributes":{ - "align":"justify" - }, - "insert":"\n" - }, - { - "insert":"Have trouble finding things? " - }, - { - "attributes":{ - "list":"ordered" - }, - "insert":"\n" - }, - { - "insert":"Just type in the search bar" - }, - { - "attributes":{ - "indent":1, - "list":"ordered" - }, - "insert":"\n" - }, - { - "insert":"and easily find contents" - }, - { - "attributes":{ - "indent":2, - "list":"ordered" - }, - "insert":"\n" - }, - { - "insert":"across projects or folders." - }, - { - "attributes":{ - "indent":2, - "list":"ordered" - }, - "insert":"\n" - }, - { - "insert":"It matches text in your note or task." - }, - { - "attributes":{ - "indent":1, - "list":"ordered" - }, - "insert":"\n" - }, - { - "insert":"Enable reminders so that you will get notified by" - }, - { - "attributes":{ - "list":"ordered" - }, - "insert":"\n" - }, - { - "insert":"email" - }, - { - "attributes":{ - "indent":1, - "list":"ordered" - }, - "insert":"\n" - }, - { - "insert":"message on your phone" - }, - { - "attributes":{ - "indent":1, - "list":"ordered" - }, - "insert":"\n" - }, - { - "insert":"popup on the web site" - }, - { - "attributes":{ - "indent":1, - "list":"ordered" - }, - "insert":"\n" - }, - { - "insert":"Create a BuJo serving as project or folder" - }, - { - "attributes":{ - "list":"bullet" - }, - "insert":"\n" - }, - { - "insert":"Organize your" - }, - { - "attributes":{ - "indent":1, - "list":"bullet" - }, - "insert":"\n" - }, - { - "insert":"tasks" - }, - { - "attributes":{ - "indent":2, - "list":"bullet" - }, - "insert":"\n" - }, - { - "insert":"notes" - }, - { - "attributes":{ - "indent":2, - "list":"bullet" - }, - "insert":"\n" - }, - { - "insert":"transactions" - }, - { - "attributes":{ - "indent":2, - "list":"bullet" - }, - "insert":"\n" - }, - { - "insert":"under BuJo " - }, - { - "attributes":{ - "indent":3, - "list":"bullet" - }, - "insert":"\n" - }, - { - "insert":"See them in Calendar" - }, - { - "attributes":{ - "list":"bullet" - }, - "insert":"\n" - }, - { - "insert":"or hierarchical view" - }, - { - "attributes":{ - "indent":1, - "list":"bullet" - }, - "insert":"\n" - }, - { - "insert": "this is a check list" - }, - { - "attributes": { - "list": "checked" - }, - "insert": "\n" - }, - { - "insert": "this is a uncheck list" - }, - { - "attributes": { - "list": "unchecked" - }, - "insert": "\n" - }, - { - "insert": "Font " - }, - { - "attributes": { - "font": "sans-serif" - }, - "insert": "Sans Serif" - }, - { - "insert": " " - }, - { - "attributes": { - "font": "serif" - }, - "insert": "Serif" - }, - { - "insert": " " - }, - { - "attributes": { - "font": "monospace" - }, - "insert": "Monospace" - }, - { - "insert": " Size " - }, - { - "attributes": { - "size": "small" - }, - "insert": "Small" - }, - { - "insert": " " - }, - { - "attributes": { - "size": "large" - }, - "insert": "Large" - }, - { - "insert": " " - }, - { - "attributes": { - "size": "huge" - }, - "insert": "Huge" - }, - { - "attributes": { - "size": "15.0" - }, - "insert": "font size 15" - }, - { - "insert": " " - }, - { - "attributes": { - "size": "35" - }, - "insert": "font size 35" - }, - { - "insert": " " - }, - { - "attributes": { - "size": "20" - }, - "insert": "font size 20" - }, - { - "attributes":{ - "token":"built_in" - }, - "insert":" diff" - }, - { - "attributes":{ - "token":"operator" - }, - "insert":"-match" - }, - { - "attributes":{ - "token":"literal" - }, - "insert":"-patch" - }, { "insert": { "image": "https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png" diff --git a/lib/src/models/documents/document.dart b/lib/src/models/documents/document.dart index d2303d15..1989b130 100644 --- a/lib/src/models/documents/document.dart +++ b/lib/src/models/documents/document.dart @@ -51,8 +51,7 @@ class Document { Stream> get changes => _observer.stream; - Delta insert(int index, Object? data, - {int replaceLength = 0, bool autoAppendNewlineAfterImage = true}) { + Delta insert(int index, Object? data, {int replaceLength = 0}) { assert(index >= 0); assert(data is String || data is Embeddable); if (data is Embeddable) { @@ -63,8 +62,7 @@ class Document { final delta = _rules.apply(RuleType.INSERT, this, index, data: data, len: replaceLength); - compose(delta, ChangeSource.LOCAL, - autoAppendNewlineAfterImage: autoAppendNewlineAfterImage); + compose(delta, ChangeSource.LOCAL); return delta; } @@ -77,8 +75,7 @@ class Document { return delta; } - Delta replace(int index, int len, Object? data, - {bool autoAppendNewlineAfterImage = true}) { + Delta replace(int index, int len, Object? data) { assert(index >= 0); assert(data is String || data is Embeddable); @@ -91,9 +88,7 @@ class Document { // We have to insert before applying delete rules // Otherwise delete would be operating on stale document snapshot. if (dataIsNotEmpty) { - delta = insert(index, data, - replaceLength: len, - autoAppendNewlineAfterImage: autoAppendNewlineAfterImage); + delta = insert(index, data, replaceLength: len); } if (len > 0) { @@ -141,17 +136,13 @@ class Document { return block.queryChild(res.offset, true); } - void compose(Delta delta, ChangeSource changeSource, - {bool autoAppendNewlineAfterImage = true, - bool autoAppendNewlineAfterVideo = true}) { + void compose(Delta delta, ChangeSource changeSource) { assert(!_observer.isClosed); delta.trim(); assert(delta.isNotEmpty); var offset = 0; - delta = _transform(delta, - autoAppendNewlineAfterImage: autoAppendNewlineAfterImage, - autoAppendNewlineAfterVideo: autoAppendNewlineAfterVideo); + delta = _transform(delta); final originalDelta = toDelta(); for (final op in delta.toList()) { final style = @@ -195,20 +186,13 @@ class Document { bool get hasRedo => _history.hasRedo; - static Delta _transform(Delta delta, - {bool autoAppendNewlineAfterImage = true, - bool autoAppendNewlineAfterVideo = true}) { + static Delta _transform(Delta delta) { final res = Delta(); final ops = delta.toList(); for (var i = 0; i < ops.length; i++) { final op = ops[i]; res.push(op); - if (autoAppendNewlineAfterImage) { - _autoAppendNewlineAfterEmbeddable(i, ops, op, res, 'image'); - } - if (autoAppendNewlineAfterVideo) { - _autoAppendNewlineAfterEmbeddable(i, ops, op, res, 'video'); - } + _autoAppendNewlineAfterEmbeddable(i, ops, op, res, 'video'); } return res; } diff --git a/lib/src/widgets/controller.dart b/lib/src/widgets/controller.dart index 22763ca0..f26765b0 100644 --- a/lib/src/widgets/controller.dart +++ b/lib/src/widgets/controller.dart @@ -106,13 +106,12 @@ class QuillController extends ChangeNotifier { void replaceText( int index, int len, Object? data, TextSelection? textSelection, - {bool ignoreFocus = false, bool autoAppendNewlineAfterImage = true}) { + {bool ignoreFocus = false}) { assert(data is String || data is Embeddable); Delta? delta; if (len > 0 || data is! String || data.isNotEmpty) { - delta = document.replace(index, len, data, - autoAppendNewlineAfterImage: autoAppendNewlineAfterImage); + delta = document.replace(index, len, data); var shouldRetainDelta = toggledStyle.isNotEmpty && delta.isNotEmpty && delta.length <= 2 && diff --git a/lib/src/widgets/text_line.dart b/lib/src/widgets/text_line.dart index 049e73c8..f9a677b6 100644 --- a/lib/src/widgets/text_line.dart +++ b/lib/src/widgets/text_line.dart @@ -39,6 +39,7 @@ class TextLine extends StatelessWidget { Widget build(BuildContext context) { assert(debugCheckHasMediaQuery(context)); + var textSpan; if (line.hasEmbed) { if (line.childCount == 1) { // For video, it is always single child @@ -48,12 +49,27 @@ class TextLine extends StatelessWidget { // The line could contain more than one Embed & more than one Text // TODO: handle more than one Embed - final embed = - line.children.firstWhere((child) => child is Embed) as Embed; - return EmbedProxy(embedBuilder(context, embed, readOnly)); - } - final textSpan = _buildTextSpan(context); + textSpan = TextSpan( + style: const TextStyle(color: Colors.black), + children: [ + const TextSpan(text: 'Flutter is'), + WidgetSpan( + child: SizedBox( + width: 50, + height: 50, + child: Image.network( + 'https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png', + fit: BoxFit.cover, + ), + )), + const TextSpan(text: 'the best!'), + ]); + } + + if (!line.hasEmbed) { + textSpan = _buildTextSpan(context); + } final strutStyle = StrutStyle.fromTextStyle(textSpan.style!); final textAlign = _getTextAlign(); final child = RichText(