From b07b26326b2b96a59828ea97271001aa8e8d96de Mon Sep 17 00:00:00 2001 From: Ellet Date: Sun, 12 Nov 2023 22:28:57 +0300 Subject: [PATCH] Html conventor package --- example/lib/pages/home_page.dart | 34 ++++- example/pubspec.yaml | 6 +- packages/README.md | 8 +- packages/flutter_quill_html/.gitignore | 30 ++++ packages/flutter_quill_html/.metadata | 10 ++ packages/flutter_quill_html/CHANGELOG.md | 3 + packages/flutter_quill_html/LICENSE | 1 + packages/flutter_quill_html/README.md | 23 +++ .../flutter_quill_html/analysis_options.yaml | 36 +++++ packages/flutter_quill_html/delta_markdown | 1 + .../lib/flutter_quill_html.dart | 132 ++++++++++++++++++ packages/flutter_quill_html/pubspec.yaml | 38 +++++ .../pubspec_overrides.yaml.g | 3 + .../test/flutter_quill_html_test.dart | 7 + scripts/disable_local_dev.sh | 5 + scripts/enable_local_dev.sh | 5 + 16 files changed, 335 insertions(+), 7 deletions(-) create mode 100644 packages/flutter_quill_html/.gitignore create mode 100644 packages/flutter_quill_html/.metadata create mode 100644 packages/flutter_quill_html/CHANGELOG.md create mode 100644 packages/flutter_quill_html/LICENSE create mode 100644 packages/flutter_quill_html/README.md create mode 100644 packages/flutter_quill_html/analysis_options.yaml create mode 160000 packages/flutter_quill_html/delta_markdown create mode 100644 packages/flutter_quill_html/lib/flutter_quill_html.dart create mode 100644 packages/flutter_quill_html/pubspec.yaml create mode 100644 packages/flutter_quill_html/pubspec_overrides.yaml.g create mode 100644 packages/flutter_quill_html/test/flutter_quill_html_test.dart diff --git a/example/lib/pages/home_page.dart b/example/lib/pages/home_page.dart index ea12eb8b..5f0db3ab 100644 --- a/example/lib/pages/home_page.dart +++ b/example/lib/pages/home_page.dart @@ -15,6 +15,7 @@ import 'package:flutter_quill/flutter_quill.dart'; import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; import 'package:flutter_quill_extensions/logic/services/image_picker/image_picker.dart'; import 'package:flutter_quill_extensions/presentation/embeds/widgets/image.dart'; +import 'package:flutter_quill_html/flutter_quill_html.dart'; import 'package:image_cropper/image_cropper.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; @@ -120,10 +121,12 @@ class _HomePageState extends State { ), ), IconButton( - onPressed: () => _insertTimeStamp( - _controller, - DateTime.now().toString(), - ), + onPressed: () { + _insertTimeStamp( + _controller, + DateTime.now().toString(), + ); + }, icon: const Icon(Icons.add_alarm_rounded), ), IconButton( @@ -264,6 +267,29 @@ class _HomePageState extends State { } }, ), + ListTile( + title: const Text('Convert to/from HTML'), + onTap: () async { + final scaffoldMessenger = ScaffoldMessenger.of(context); + final navigator = Navigator.of(context); + try { + final html = _controller.document.toDelta().toHtml(); + print(html); + _controller.document = + Document.fromDelta(DeltaHtmlExt.fromHtml(html)); + } catch (e) { + scaffoldMessenger.showSnackBar( + SnackBar( + content: Text( + 'Error while convert to/from HTML: ${e.toString()}', + ), + ), + ); + } finally { + navigator.pop(); + } + }, + ), _buildMenuBar(context), ], ), diff --git a/example/pubspec.yaml b/example/pubspec.yaml index ccdf1958..38103a28 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -17,8 +17,10 @@ dependencies: path_provider: ^2.1.1 # filesystem_picker: ^4.0.0 file_picker: ^6.1.1 - flutter_quill: ^8.4.1 - flutter_quill_extensions: ^0.6.5 + flutter_quill: ^8.5.1 + flutter_quill_extensions: ^0.6.7 + flutter_quill_html: + path: ../packages/flutter_quill_html path: ^1.8.3 desktop_drop: ^0.4.4 image_cropper: ^5.0.0 diff --git a/packages/README.md b/packages/README.md index 6da077ab..c30ba2bb 100644 --- a/packages/README.md +++ b/packages/README.md @@ -3,4 +3,10 @@ This folder contains packages that add more features to the [FlutterQuill](../README.md) that might be outside of the packages main purpose -This page will be updated soon. \ No newline at end of file +## Table of contents +- [Flutter Quill Packages](#flutter-quill-packages) + - [Table of contents](#table-of-contents) + - [Packages](#packages) + +## Packages +- [flutter_quill_html](./flutter_quill_html/) \ No newline at end of file diff --git a/packages/flutter_quill_html/.gitignore b/packages/flutter_quill_html/.gitignore new file mode 100644 index 00000000..96486fd9 --- /dev/null +++ b/packages/flutter_quill_html/.gitignore @@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/packages/flutter_quill_html/.metadata b/packages/flutter_quill_html/.metadata new file mode 100644 index 00000000..6176c000 --- /dev/null +++ b/packages/flutter_quill_html/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "d211f42860350d914a5ad8102f9ec32764dc6d06" + channel: "stable" + +project_type: package diff --git a/packages/flutter_quill_html/CHANGELOG.md b/packages/flutter_quill_html/CHANGELOG.md new file mode 100644 index 00000000..41cc7d81 --- /dev/null +++ b/packages/flutter_quill_html/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/packages/flutter_quill_html/LICENSE b/packages/flutter_quill_html/LICENSE new file mode 100644 index 00000000..ba75c69f --- /dev/null +++ b/packages/flutter_quill_html/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/packages/flutter_quill_html/README.md b/packages/flutter_quill_html/README.md new file mode 100644 index 00000000..9c30d57e --- /dev/null +++ b/packages/flutter_quill_html/README.md @@ -0,0 +1,23 @@ +# Flutter Quill HTML +A extension for [flutter_quill](https://pub.dev/packages/flutter_quill) package to add support for dealing with conversion to/from html + +It uses [vsc_quill_delta_to_html](https://pub.dev/packages/vsc_quill_delta_to_html) package to convert the the delta to HTML + +## Features + +```markdown +- Easy to use +- Support with Flutter Quill package +``` + +## Getting started + +This will be updated soon. + +## Usage + +Take a look at the example of the repo, This will be updated soon. + +## Additional information + +This will be updated soon. diff --git a/packages/flutter_quill_html/analysis_options.yaml b/packages/flutter_quill_html/analysis_options.yaml new file mode 100644 index 00000000..a8fe6f69 --- /dev/null +++ b/packages/flutter_quill_html/analysis_options.yaml @@ -0,0 +1,36 @@ +include: package:flutter_lints/flutter.yaml + +analyzer: + errors: + undefined_prefixed_name: ignore + unsafe_html: ignore +linter: + rules: + always_declare_return_types: true + always_put_required_named_parameters_first: true + annotate_overrides: true + avoid_empty_else: true + avoid_escaping_inner_quotes: true + avoid_print: true + avoid_redundant_argument_values: true + avoid_types_on_closure_parameters: true + avoid_void_async: true + cascade_invocations: true + directives_ordering: true + omit_local_variable_types: true + prefer_const_constructors: true + prefer_const_constructors_in_immutables: true + prefer_const_declarations: true + prefer_final_fields: true + prefer_final_in_for_each: true + prefer_final_locals: true + prefer_initializing_formals: true + prefer_int_literals: true + prefer_interpolation_to_compose_strings: true + prefer_relative_imports: true + prefer_single_quotes: true + sort_constructors_first: true + sort_unnamed_constructors_first: true + unnecessary_lambdas: true + unnecessary_parenthesis: true + unnecessary_string_interpolations: true \ No newline at end of file diff --git a/packages/flutter_quill_html/delta_markdown b/packages/flutter_quill_html/delta_markdown new file mode 160000 index 00000000..22e49a1a --- /dev/null +++ b/packages/flutter_quill_html/delta_markdown @@ -0,0 +1 @@ +Subproject commit 22e49a1abe72894f6666baedcc00fb89f13d2c4a diff --git a/packages/flutter_quill_html/lib/flutter_quill_html.dart b/packages/flutter_quill_html/lib/flutter_quill_html.dart new file mode 100644 index 00000000..1657b872 --- /dev/null +++ b/packages/flutter_quill_html/lib/flutter_quill_html.dart @@ -0,0 +1,132 @@ +library flutter_quill_html; + +import 'dart:convert' show jsonDecode; + +import 'package:delta_markdown/delta_markdown.dart' show markdownToDelta; +import 'package:flutter/foundation.dart'; +import 'package:flutter_quill/flutter_quill.dart' show Delta; +// ignore: depend_on_referenced_packages +import 'package:html/dom.dart' as html_dom; +// ignore: depend_on_referenced_packages +import 'package:html/parser.dart' as html_parse; +import 'package:html2md/html2md.dart' as html2md; +import 'package:vsc_quill_delta_to_html/vsc_quill_delta_to_html.dart' + as conventer show ConverterOptions, QuillDeltaToHtmlConverter; + +typedef ConverterOptions = conventer.ConverterOptions; + +extension DeltaHtmlExt on Delta { + String toHtml({ConverterOptions? options}) { + final html = conventer.QuillDeltaToHtmlConverter( + List.castFrom(toJson()), + options, + ).convert(); + return html; + } + + static Delta fromHtml(String html) { + return Delta.fromJson( + jsonDecode( + markdownToDelta( + html2md.convert(html), + ), + ), + ); + } +} + +// From https://github.com/singerdmx/flutter-quill/issues/1100#issuecomment-1681274676 +@immutable +class HtmlToDeltaConverter { + static const _collorPattern = r'color: rgb\((\d+), (\d+), (\d+)\);'; + + static Delta _parseInlineStyles(html_dom.Element element) { + var delta = Delta(); + + for (final node in element.nodes) { + final attributes = _parseElementStyles(element); + + if (node is html_dom.Text) { + delta.insert(node.text, attributes); + } else if (node is html_dom.Element && node.localName == 'img') { + final src = node.attributes['src']; + if (src != null) { + delta.insert({'image': src}); + } + } else if (node is html_dom.Element) { + delta = delta.concat(_parseInlineStyles(node)); + } + } + + return delta; + } + + static Map _parseElementStyles(html_dom.Element element) { + final attributes = {}; + + if (element.localName == 'strong') attributes['bold'] = true; + if (element.localName == 'em') attributes['italic'] = true; + if (element.localName == 'u') attributes['underline'] = true; + if (element.localName == 'del') attributes['strike'] = true; + + final style = element.attributes['style']; + if (style != null) { + final colorValue = _parseColorFromStyle(style); + if (colorValue != null) attributes['color'] = colorValue; + + final bgColorValue = _parseBackgroundColorFromStyle(style); + if (bgColorValue != null) attributes['background'] = bgColorValue; + } + + return attributes; + } + + static String? _parseColorFromStyle(String style) { + if (RegExp(r'(^|\s)color:(\s|$)').hasMatch(style)) { + return _parseRgbColorFromMatch(RegExp(_collorPattern).firstMatch(style)); + } + return null; + } + + static String? _parseBackgroundColorFromStyle(String style) { + if (RegExp(r'(^|\s)background-color:(\s|$)').hasMatch(style)) { + return _parseRgbColorFromMatch(RegExp(_collorPattern).firstMatch(style)); + } + return null; + } + + static String? _parseRgbColorFromMatch(RegExpMatch? colorMatch) { + if (colorMatch != null) { + try { + final red = int.parse(colorMatch.group(1)!); + final green = int.parse(colorMatch.group(2)!); + final blue = int.parse(colorMatch.group(3)!); + return '#${red.toRadixString(16).padLeft(2, '0')}${green.toRadixString(16).padLeft(2, '0')}${blue.toRadixString(16).padLeft(2, '0')}'; + } catch (e) { + // debugPrintStack(label: e.toString()); + } + } + return null; + } + + static Delta htmlToDelta(String html) { + final document = html_parse.parse(html); + var delta = Delta(); + + for (final node in document.body?.nodes ?? []) { + if (node is html_dom.Element) { + switch (node.localName) { + case 'p': + delta = delta.concat(_parseInlineStyles(node))..insert('\n'); + break; + case 'br': + delta.insert('\n'); + break; + } + } + } + + return html.isNotEmpty ? delta : Delta() + ..insert('\n'); + } +} diff --git a/packages/flutter_quill_html/pubspec.yaml b/packages/flutter_quill_html/pubspec.yaml new file mode 100644 index 00000000..00d11672 --- /dev/null +++ b/packages/flutter_quill_html/pubspec.yaml @@ -0,0 +1,38 @@ +name: flutter_quill_html +description: A extension for flutter_quill package to add support for dealing with conversion to/from html +version: 0.0.1-experimental.1 +homepage: https://github.com/singerdmx/flutter-quill/tree/master/packages/flutter_quill_html +repository: https://github.com/singerdmx/flutter-quill/tree/master/packages/flutter_quill_html + +topics: + - ui + - widgets + - widget + - rich-text-editor + - quill + +environment: + sdk: '>=3.1.5 <4.0.0' + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + flutter_quill: ^8.5.1 + vsc_quill_delta_to_html: ^1.0.3 + html2md: ^1.3.1 + delta_markdown: + path: ./delta_markdown + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^3.0.1 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + uses-material-design: true \ No newline at end of file diff --git a/packages/flutter_quill_html/pubspec_overrides.yaml.g b/packages/flutter_quill_html/pubspec_overrides.yaml.g new file mode 100644 index 00000000..844dcdea --- /dev/null +++ b/packages/flutter_quill_html/pubspec_overrides.yaml.g @@ -0,0 +1,3 @@ +dependency_overrides: + flutter_quill: + path: ../../ \ No newline at end of file diff --git a/packages/flutter_quill_html/test/flutter_quill_html_test.dart b/packages/flutter_quill_html/test/flutter_quill_html_test.dart new file mode 100644 index 00000000..cd2cad61 --- /dev/null +++ b/packages/flutter_quill_html/test/flutter_quill_html_test.dart @@ -0,0 +1,7 @@ +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('No tests for now', () { + expect(true, true); + }); +} diff --git a/scripts/disable_local_dev.sh b/scripts/disable_local_dev.sh index def60bbb..96c00359 100755 --- a/scripts/disable_local_dev.sh +++ b/scripts/disable_local_dev.sh @@ -19,4 +19,9 @@ rm flutter_quill_test/pubspec_overrides.yaml echo "" +echo "Disable local development for all the other packages..." +rm packages/flutter_quill_html/pubspec_overrides.yaml + +echo "" + echo "Local development for all libraries has been disabled, please 'flutter pub get' for each one of them" \ No newline at end of file diff --git a/scripts/enable_local_dev.sh b/scripts/enable_local_dev.sh index 6e2fda39..39e7288c 100755 --- a/scripts/enable_local_dev.sh +++ b/scripts/enable_local_dev.sh @@ -19,4 +19,9 @@ cp flutter_quill_test/pubspec_overrides.yaml.g flutter_quill_test/pubspec_overri echo "" +echo "Enable local development for all the other packages..." +cp packages/flutter_quill_html/pubspec_overrides.yaml.g packages/flutter_quill_html/pubspec_overrides.yaml + +echo "" + echo "Local development for all libraries has been enabled, please 'flutter pub get' for each one of them" \ No newline at end of file