Merge branch 'singerdmx:master' into master-fetch-current

pull/756/head
JasmitSingh90 3 years ago committed by GitHub
commit d04a105de0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .gitignore
  2. 33
      CHANGELOG.md
  3. 9
      README.md
  4. 88
      example/ios/Runner.xcodeproj/project.pbxproj
  5. 2
      example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  6. 2
      example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
  7. 3
      example/ios/Runner.xcworkspace/contents.xcworkspacedata
  8. 2
      example/windows/flutter/generated_plugin_registrant.cc
  9. 2
      example/windows/flutter/generated_plugin_registrant.h
  10. 13
      lib/src/models/documents/attribute.dart
  11. 6
      lib/src/models/rules/insert.dart
  12. 64
      lib/src/translations/toolbar.i18n.dart
  13. 11
      lib/src/utils/delta.dart
  14. 12
      lib/src/utils/platform.dart
  15. 4
      lib/src/widgets/controller.dart
  16. 2
      lib/src/widgets/default_styles.dart
  17. 41
      lib/src/widgets/editor.dart
  18. 1
      lib/src/widgets/embeds/default_embed_builder.dart
  19. 29
      lib/src/widgets/embeds/image.dart
  20. 857
      lib/src/widgets/raw_editor.dart
  21. 5
      lib/src/widgets/text_block.dart
  22. 22
      lib/src/widgets/toolbar.dart
  23. 11
      lib/src/widgets/toolbar/link_style_button.dart
  24. 5
      pubspec.yaml

1
.gitignore vendored

@ -66,6 +66,7 @@ build/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
example/ios/Podfile.lock
# Exceptions to above rules.
!**/ios/**/default.mode1v3

@ -1,3 +1,36 @@
# [4.0.5]
* Fixed casting null to Tuple2 when link dialog is dismissed without any input (e.g. barrier dismissed).
# [4.0.4]
* Bug fix for text direction rtl.
# [4.0.3]
* Support text direction rtl.
# [4.0.2]
* Clear toggled style on selection change.
# [4.0.1]
* Fix copy/cut/paste/selectAll not working.
# [4.0.0]
* Upgrade for Flutter 2.10.
# [3.9.11]
* Added Indonesian translation.
# [3.9.10]
* Fix for undoing a modification ending with an indented line.
# [3.9.9]
* iOS: Save image whose filename does not end with image file extension.
# [3.9.8]
* Added Urdu translation.
# [3.9.7]
* Fix for clicking on the Link button without any text on a new line crashes.
# [3.9.6]
* Apply locale to QuillEditor(contents).

@ -110,13 +110,13 @@ Define `mobileWidth`, `mobileHeight`, `mobileMargin`, `mobileAlignment` as follo
}
```
## Translation of toolbar
The package offers translations for the quill toolbar, it will follow the system locale unless you set your own locale with:
## Translation
The package offers translations for the quill toolbar and editor, it will follow the system locale unless you set your own locale with:
```
QuillToolbar(locale: Locale('fr'), ...)
QuillEditor(locale: Locale('fr'), ...)
```
Currently, translations are available for these locales:
Currently, translations are available for these 17 locales:
* `Locale('en')`
* `Locale('ar')`
* `Locale('de')`
@ -128,9 +128,12 @@ Currently, translations are available for these locales:
* `Locale('es')`
* `Locale('tr')`
* `Locale('uk')`
* `Locale('ur')`
* `Locale('pt')`
* `Locale('pl')`
* `Locale('vi')`
* `Locale('id')`
* `Locale('no')`
### Contributing to translations
The translation file is located at [lib/src/translations/toolbar.i18n.dart](lib/src/translations/toolbar.i18n.dart). Feel free to contribute your own translations, just copy the English translations map and replace the values with your translations. Then open a pull request so everyone can benefit from your translations!

@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
@ -14,6 +14,7 @@
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
D290BBC2BCE42906E260DD85 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC245C6D0FF6BF1D0B733C5B /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@ -32,7 +33,10 @@
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
2F435AE316A2CEF9DB2FCB44 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
45FB9682812B691627A14497 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
48A1E3B04AC3F3F79EE01940 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
@ -44,6 +48,7 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
DC245C6D0FF6BF1D0B733C5B /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -51,12 +56,24 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D290BBC2BCE42906E260DD85 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
54D582D77359D2F1CFEABAD6 /* Pods */ = {
isa = PBXGroup;
children = (
2F435AE316A2CEF9DB2FCB44 /* Pods-Runner.debug.xcconfig */,
48A1E3B04AC3F3F79EE01940 /* Pods-Runner.release.xcconfig */,
45FB9682812B691627A14497 /* Pods-Runner.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
@ -74,7 +91,8 @@
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
CF3B75C9A7D2FA2A4C99F110 /* Frameworks */,
54D582D77359D2F1CFEABAD6 /* Pods */,
9B5485B940DE97CC1A7B761C /* Frameworks */,
);
sourceTree = "<group>";
};
@ -110,6 +128,14 @@
name = "Supporting Files";
sourceTree = "<group>";
};
9B5485B940DE97CC1A7B761C /* Frameworks */ = {
isa = PBXGroup;
children = (
DC245C6D0FF6BF1D0B733C5B /* Pods_Runner.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -117,12 +143,14 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
C781F89B47F2E7DB7E2B4898 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
869AA06D856BCA582968F111 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@ -139,7 +167,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1020;
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
@ -194,6 +222,23 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
869AA06D856BCA582968F111 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@ -208,6 +253,28 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
C781F89B47F2E7DB7E2B4898 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@ -305,7 +372,10 @@
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
@ -433,7 +503,10 @@
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
@ -456,7 +529,10 @@
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",

@ -2,6 +2,6 @@
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
location = "self:">
</FileRef>
</Workspace>

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

@ -4,4 +4,7 @@
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

@ -2,6 +2,8 @@
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
#include <url_launcher_windows/url_launcher_windows.h>

@ -2,6 +2,8 @@
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_

@ -32,6 +32,7 @@ class Attribute<T> {
Attribute.placeholder.key: Attribute.placeholder,
Attribute.header.key: Attribute.header,
Attribute.align.key: Attribute.align,
Attribute.direction.key: Attribute.direction,
Attribute.list.key: Attribute.list,
Attribute.codeBlock.key: Attribute.codeBlock,
Attribute.blockQuote.key: Attribute.blockQuote,
@ -79,6 +80,8 @@ class Attribute<T> {
static final BlockQuoteAttribute blockQuote = BlockQuoteAttribute();
static final DirectionAttribute direction = DirectionAttribute(null);
static final WidthAttribute width = WidthAttribute(null);
static final HeightAttribute height = HeightAttribute(null);
@ -116,6 +119,7 @@ class Attribute<T> {
Attribute.codeBlock.key,
Attribute.blockQuote.key,
Attribute.indent.key,
Attribute.direction.key,
});
static final Set<String> blockKeysExceptHeader = LinkedHashSet.of({
@ -124,6 +128,7 @@ class Attribute<T> {
Attribute.codeBlock.key,
Attribute.blockQuote.key,
Attribute.indent.key,
Attribute.direction.key,
});
static final Set<String> exclusiveBlockKeys = LinkedHashSet.of({
@ -163,6 +168,9 @@ class Attribute<T> {
// "attributes":{"list":"unchecked"}
static Attribute<String?> get unchecked => ListAttribute('unchecked');
// "attributes":{"direction":"rtl"}
static Attribute<String?> get rtl => DirectionAttribute('rtl');
// "attributes":{"indent":1"}
static Attribute<int?> get indentL1 => IndentAttribute(level: 1);
@ -309,6 +317,11 @@ class BlockQuoteAttribute extends Attribute<bool> {
BlockQuoteAttribute() : super('blockquote', AttributeScope.BLOCK, true);
}
class DirectionAttribute extends Attribute<String?> {
DirectionAttribute(String? val)
: super('direction', AttributeScope.BLOCK, val);
}
class WidthAttribute extends Attribute<String?> {
WidthAttribute(String? val) : super('width', AttributeScope.IGNORE, val);
}

@ -115,7 +115,11 @@ class PreserveBlockStyleOnInsertRule extends InsertRule {
delta.insert('\n', lineStyle.toJson());
} else if (i < lines.length - 1) {
// we don't want to insert a newline after the last chunk of text, so -1
delta.insert('\n', blockStyle);
final blockAttributes = blockStyle.isEmpty
? null
: blockStyle.map<String, dynamic>((_, attribute) =>
MapEntry<String, dynamic>(attribute.key, attribute.value));
delta.insert('\n', blockAttributes);
}
}

@ -141,8 +141,8 @@ extension Localization on String {
'Text': '文字',
'What is entered is not a link': '输入的不是链接',
'Resize': '调整大小',
'Width': 'Width',
'Height': 'Height',
'Width': '宽度',
'Height': '高度',
},
'ko': {
'Paste a link': '링크를 붙여넣어 주세요.',
@ -308,6 +308,66 @@ extension Localization on String {
'Width': 'Width',
'Height': 'Height',
},
'ur': {
'Paste a link': 'لنک پیسٹ کریں',
'Ok': 'ٹھیک ہے',
'Select Color': 'رنگ منتخب کریں',
'Gallery': 'گیلری',
'Link': 'لنک',
'Please first select some text to transform into a link.':
'براہ کرم لنک میں تبدیل کرنے کے لیے پہلے کچھ متن منتخب کریں۔',
'Open': 'کھولیں',
'Copy': 'نقل',
'Remove': 'ہٹا دیں',
'Save': 'محفوظ کریں',
'Zoom': 'زوم',
'Saved': 'محفوظ کر لیا',
'Text': 'متن',
'What is entered is not a link': 'جو درج کیا گیا ہے وہ لنک نہیں ہے۔',
'Resize': 'سائز تبدیل کریں۔',
'Width': 'چوڑائی',
'Height': 'اونچائی',
},
'id': {
'Paste a link': 'Tempel tautan',
'Ok': 'Oke',
'Select Color': 'Pilih Warna',
'Gallery': 'Galeri',
'Link': 'Tautan',
'Please first select some text to transform into a link.':
'Silakan pilih dulu beberapa teks untuk diubah menjadi tautan.',
'Open': 'Buka',
'Copy': 'Salin',
'Remove': 'Hapus',
'Save': 'Simpan',
'Zoom': 'Perbesar',
'Saved': 'Tersimpan',
'Text': 'Teks',
'What is entered is not a link': 'Yang dimasukkan bukan tautan',
'Resize': 'Ubah Ukuran',
'Width': 'Lebar',
'Height': 'Tinggi',
},
'no': {
'Paste a link': 'Lim inn lenke',
'Ok': 'Ok',
'Select Color': 'Velg farge',
'Gallery': 'Galleri',
'Link': 'Lenke',
'Please first select some text to transform into a link.':
'Velg først litt tekst for å forvandle til en lenke.',
'Open': 'Åpne',
'Copy': 'Kopier',
'Remove': 'Fjern',
'Save': 'Lagre',
'Zoom': 'Zoom',
'Saved': 'Lagret',
'Text': 'Tekst',
'What is entered is not a link': 'Du har oppgitt en ugyldig lenke',
'Resize': 'Endre størrelse',
'Width': 'Bredde',
'Height': 'Høyde',
},
};
String get i18n => localize(this, _t);

@ -1,5 +1,8 @@
import 'dart:math' as math;
import 'dart:ui';
import '../models/documents/attribute.dart';
import '../models/documents/nodes/node.dart';
import '../models/quill_delta.dart';
// Diff between two texts - old text and new text
@ -72,3 +75,11 @@ int getPositionDelta(Delta user, Delta actual) {
}
return diff;
}
TextDirection getDirectionOfNode(Node node) {
final direction = node.style.attributes[Attribute.direction.key];
if (direction == Attribute.rtl) {
return TextDirection.rtl;
}
return TextDirection.ltr;
}

@ -1,3 +1,6 @@
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
bool isMobile([TargetPlatform? targetPlatform]) {
@ -23,3 +26,12 @@ bool isAppleOS([TargetPlatform? targetPlatform]) {
TargetPlatform.iOS,
}.contains(targetPlatform);
}
Future<bool> isIOSSimulator() async {
if (Platform.isIOS) {
final deviceInfo = DeviceInfoPlugin();
final iosInfo = await deviceInfo.iosInfo;
return !iosInfo.isPhysicalDevice;
}
return false;
}

@ -23,6 +23,7 @@ class QuillController extends ChangeNotifier {
this.onReplaceText,
this.onDelete,
this.onSelectionCompleted,
this.onSelectionChanged,
}) : _selection = selection,
_keepStyleOnNewLine = keepStyleOnNewLine;
@ -52,6 +53,7 @@ class QuillController extends ChangeNotifier {
DeleteCallback? onDelete;
void Function()? onSelectionCompleted;
void Function(TextSelection textSelection)? onSelectionChanged;
/// Store any styles attribute that got toggled by the tap of a button
/// and that has not been applied yet.
@ -327,6 +329,8 @@ class QuillController extends ChangeNotifier {
_selection = selection.copyWith(
baseOffset: math.min(selection.baseOffset, end),
extentOffset: math.min(selection.extentOffset, end));
toggledStyle = Style();
onSelectionChanged?.call(textSelection);
}
/// Given offset, find its leaf node in document

@ -202,7 +202,7 @@ class DefaultStyles {
final inlineCodeStyle = TextStyle(
fontSize: 14,
color: themeData.colorScheme.primaryVariant.withOpacity(0.8),
color: themeData.colorScheme.primary.withOpacity(0.8),
fontFamily: fontFamily,
);

@ -342,7 +342,7 @@ class QuillEditor extends StatefulWidget {
final CustomStyleBuilder? customStyleBuilder;
/// The locale to use for the editor toolbar, defaults to system locale
/// and more https://github.com/singerdmx/flutter-quill#translation-of-toolbar
/// More https://github.com/singerdmx/flutter-quill#translation
final Locale? locale;
/// Delegate function responsible for showing menu with link actions on
@ -1544,16 +1544,47 @@ class RenderEditor extends RenderEditableContainerBox
// End TextLayoutMetrics implementation
QuillVerticalCaretMovementRun startVerticalCaretMovement(
TextPosition startPosition) {
return QuillVerticalCaretMovementRun._(
this,
startPosition,
);
}
@override
void systemFontsDidChange() {
super.systemFontsDidChange();
markNeedsLayout();
}
}
void debugAssertLayoutUpToDate() {
// no-op?
// this assert was added by Flutter TextEditingActionTarge
// so we have to comply here.
class QuillVerticalCaretMovementRun
extends BidirectionalIterator<TextPosition> {
QuillVerticalCaretMovementRun._(
this._editor,
this._currentTextPosition,
);
TextPosition _currentTextPosition;
final RenderEditor _editor;
@override
TextPosition get current {
return _currentTextPosition;
}
@override
bool moveNext() {
_currentTextPosition = _editor.getTextPositionBelow(_currentTextPosition);
return true;
}
@override
bool movePrevious() {
_currentTextPosition = _editor.getTextPositionAbove(_currentTextPosition);
return true;
}
}

@ -162,6 +162,7 @@ Widget _menuOptionsForReadonlyImage(
color: Colors.greenAccent,
text: 'Save'.i18n,
onPressed: () {
imageUrl = appendFileExtensionToImageUrl(imageUrl);
GallerySaver.saveImage(imageUrl).then((_) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('Saved'.i18n)));

@ -12,6 +12,16 @@ import '../../models/documents/nodes/leaf.dart';
import '../../models/documents/style.dart';
import '../controller.dart';
const List<String> imageFileExtensions = [
'.jpeg',
'.png',
'.jpg',
'.gif',
'.webp',
'.tif',
'.heic'
];
bool isImageBase64(String imageUrl) {
return !imageUrl.startsWith('http') && isBase64(imageUrl);
}
@ -64,6 +74,25 @@ String standardizeImageUrl(String url) {
return url;
}
/// This is a bug of Gallery Saver Package.
/// It can not save image that's filename does not end with it's file extension
/// like below.
// "https://firebasestorage.googleapis.com/v0/b/eventat-4ba96.appspot.com/o/2019-Metrology-Events.jpg?alt=media&token=bfc47032-5173-4b3f-86bb-9659f46b362a"
/// If imageUrl does not end with it's file extension,
/// file extension is added to image url for saving.
String appendFileExtensionToImageUrl(String url) {
final endsWithImageFileExtension = imageFileExtensions
.firstWhere((s) => url.toLowerCase().endsWith(s), orElse: () => '');
if (endsWithImageFileExtension.isNotEmpty) {
return url;
}
final imageFileExtension = imageFileExtensions
.firstWhere((s) => url.toLowerCase().contains(s), orElse: () => '');
return url + imageFileExtension;
}
class ImageTapWrapper extends StatelessWidget {
const ImageTapWrapper({
required this.imageUrl,

File diff suppressed because it is too large Load Diff

@ -5,6 +5,7 @@ import 'package:tuple/tuple.dart';
import '../../flutter_quill.dart';
import '../models/documents/nodes/block.dart';
import '../models/documents/nodes/line.dart';
import '../utils/delta.dart';
import 'box.dart';
import 'cursor.dart';
import 'delegate.dart';
@ -146,7 +147,9 @@ class EditableTextBlock extends StatelessWidget {
hasFocus,
MediaQuery.of(context).devicePixelRatio,
cursorCont);
children.add(editableTextLine);
final nodeTextDirection = getDirectionOfNode(line);
children.add(Directionality(
textDirection: nodeTextDirection, child: editableTextLine));
}
return children.toList(growable: false);
}

@ -100,6 +100,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
bool showImageButton = true,
bool showVideoButton = true,
bool showCameraButton = true,
bool showDirection = false,
OnImagePickCallback? onImagePickCallback,
OnVideoPickCallback? onVideoPickCallback,
MediaPickSettingSelector? mediaPickSettingSelector,
@ -114,13 +115,8 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
///shown when embedding an image, for example
QuillDialogTheme? dialogTheme,
///The locale to use for the editor toolbar, defaults to system locale
///Currently the supported locales are:
/// * Locale('en')
/// * Locale('de')
/// * Locale('fr')
/// * Locale('zh')
/// and more https://github.com/singerdmx/flutter-quill#translation-of-toolbar
/// The locale to use for the editor toolbar, defaults to system locale
/// More at https://github.com/singerdmx/flutter-quill#translation
Locale? locale,
Key? key,
}) {
@ -136,7 +132,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
showClearFormat ||
onImagePickCallback != null ||
onVideoPickCallback != null,
showAlignmentButtons,
showAlignmentButtons || showDirection,
showLeftAlignment,
showCenterAlignment,
showRightAlignment,
@ -301,6 +297,14 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
showRightAlignment: showRightAlignment,
showJustifyAlignment: showJustifyAlignment,
),
if (showDirection)
ToggleStyleButton(
attribute: Attribute.rtl,
controller: controller,
icon: Icons.format_textdirection_r_to_l,
iconSize: toolbarIconSize,
iconTheme: iconTheme,
),
if (showDividers &&
isButtonGroupShown[1] &&
(isButtonGroupShown[2] ||
@ -425,7 +429,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
final FilePickImpl? filePickImpl;
/// The locale to use for the editor toolbar, defaults to system locale
/// and more https://github.com/singerdmx/flutter-quill#translation-of-toolbar
/// More https://github.com/singerdmx/flutter-quill#translation
final Locale? locale;
@override

@ -114,12 +114,17 @@ class _LinkStyleButtonState extends State<LinkStyleButton> {
}
}
text ??= widget.controller.document
.getPlainText(index, widget.controller.selection.end - index);
final len = widget.controller.selection.end - index;
text ??=
len == 0 ? '' : widget.controller.document.getPlainText(index, len);
return _LinkDialog(
dialogTheme: widget.dialogTheme, link: link, text: text);
},
).then(_linkSubmitted);
).then(
(value) {
if (value != null) _linkSubmitted(value);
},
);
}
String? _getLinkAttributeValue() {

@ -1,13 +1,13 @@
name: flutter_quill
description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us)
version: 3.9.6
version: 4.0.5
#author: bulletjournal
homepage: https://bulletjournal.us/home/index.html
repository: https://github.com/singerdmx/flutter-quill
environment:
sdk: ">=2.12.0 <3.0.0"
flutter: ">=2.5.3"
flutter: ">=2.10.0"
dependencies:
flutter:
@ -32,6 +32,7 @@ dependencies:
diff_match_patch: ^0.4.1
i18n_extension: ^4.2.0
gallery_saver: ^2.3.2
device_info_plus: ^3.2.1
dev_dependencies:
flutter_test:

Loading…
Cancel
Save