Support inline image

pull/324/head
Xin Yao 4 years ago
parent 6aaf351277
commit 2a92287c64
  1. 514
      example/assets/sample_data.json
  2. 32
      lib/src/models/documents/document.dart
  3. 5
      lib/src/widgets/controller.dart
  4. 26
      lib/src/widgets/text_line.dart

@ -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"

@ -51,8 +51,7 @@ class Document {
Stream<Tuple3<Delta, Delta, ChangeSource>> 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;
}

@ -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 &&

@ -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: <InlineSpan>[
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(

Loading…
Cancel
Save