From 8c2cca7d7b23d78f1f889a46c625fe8ee2c4edfb Mon Sep 17 00:00:00 2001 From: Vincent Chan Date: Mon, 15 Aug 2022 12:17:45 +0800 Subject: [PATCH 1/7] feat: copy h1/h2/h3 styles --- .../lib/src/infra/html_converter.dart | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/src/infra/html_converter.dart b/frontend/app_flowy/packages/flowy_editor/lib/src/infra/html_converter.dart index 4a15b54859..0f8578b5c7 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/src/infra/html_converter.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/src/infra/html_converter.dart @@ -427,8 +427,10 @@ class NodesToHTMLConverter { html.Element _textNodeToHtml(TextNode textNode, {int? end}) { String? subType = textNode.attributes["subtype"]; + String? heading = textNode.attributes["heading"]; return _deltaToHtml(textNode.delta, subType: subType, + heading: heading, end: end, checked: textNode.attributes["checkbox"] == true); } @@ -501,7 +503,7 @@ class NodesToHTMLConverter { /// Text /// ``` html.Element _deltaToHtml(Delta delta, - {String? subType, int? end, bool? checked}) { + {String? subType, String? heading, int? end, bool? checked}) { if (end != null) { delta = delta.slice(0, end); } @@ -517,6 +519,14 @@ class NodesToHTMLConverter { node.attributes["checked"] = "true"; } childNodes.add(node); + } else if (subType == StyleKey.heading) { + if (heading == StyleKey.h1) { + tagName = tagH1; + } else if (heading == StyleKey.h2) { + tagName = tagH2; + } else if (heading == StyleKey.h3) { + tagName = tagH3; + } } for (final op in delta) { @@ -557,7 +567,10 @@ class NodesToHTMLConverter { } } - if (tagName != tagParagraph) { + if (tagName != tagParagraph && + tagName != tagH1 && + tagName != tagH2 && + tagName != tagH3) { final p = html.Element.tag(tagParagraph); for (final node in childNodes) { p.append(node); From 81d4c8d23bc7b69615574209c8615854c75a5ec1 Mon Sep 17 00:00:00 2001 From: Vincent Chan Date: Mon, 15 Aug 2022 12:51:32 +0800 Subject: [PATCH 2/7] feat: blockquote --- .../lib/src/infra/html_converter.dart | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/src/infra/html_converter.dart b/frontend/app_flowy/packages/flowy_editor/lib/src/infra/html_converter.dart index 0f8578b5c7..5963d5955d 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/src/infra/html_converter.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/src/infra/html_converter.dart @@ -24,6 +24,7 @@ const String tagDel = "del"; const String tagStrong = "strong"; const String tagSpan = "span"; const String tagCode = "code"; +const String tagBlockQuote = "blockquote"; extension on Color { String toRgbaString() { @@ -70,6 +71,8 @@ class HTMLToNodesConverter { } else { result.add(_handleRichText(child)); } + } else if (child.localName == tagBlockQuote) { + result.addAll(_handleBlockQuote(child)); } else { result.addAll(_handleElement(child)); } @@ -83,6 +86,18 @@ class HTMLToNodesConverter { return result; } + List _handleBlockQuote(html.Element element) { + final result = []; + + for (final child in element.nodes.toList()) { + if (child is html.Element) { + result.addAll(_handleElement(child, {"subtype": "quote"})); + } + } + + return result; + } + List _handleBTag(html.Element element) { final childNodes = element.nodes; return _handleContainer(childNodes); @@ -527,6 +542,8 @@ class NodesToHTMLConverter { } else if (heading == StyleKey.h3) { tagName = tagH3; } + } else if (subType == StyleKey.quote) { + tagName = tagBlockQuote; } for (final op in delta) { @@ -567,10 +584,19 @@ class NodesToHTMLConverter { } } - if (tagName != tagParagraph && + if (tagName == tagBlockQuote) { + final p = html.Element.tag(tagParagraph); + for (final node in childNodes) { + p.append(node); + } + final blockQuote = html.Element.tag(tagName); + blockQuote.append(p); + return blockQuote; + } else if (tagName != tagParagraph && tagName != tagH1 && tagName != tagH2 && - tagName != tagH3) { + tagName != tagH3 && + tagName != tagBlockQuote) { final p = html.Element.tag(tagParagraph); for (final node in childNodes) { p.append(node); From 0def9e888265366bc4da900f850a19d12f932986 Mon Sep 17 00:00:00 2001 From: Vincent Chan Date: Mon, 15 Aug 2022 13:58:36 +0800 Subject: [PATCH 3/7] feat: remove document.json from repo --- .../flowy_editor/example/assets/document.json | 245 ------------------ .../flowy_editor/example/pubspec.yaml | 5 +- .../packages/flowy_editor/pubspec.yaml | 1 - 3 files changed, 1 insertion(+), 250 deletions(-) delete mode 100644 frontend/app_flowy/packages/flowy_editor/example/assets/document.json diff --git a/frontend/app_flowy/packages/flowy_editor/example/assets/document.json b/frontend/app_flowy/packages/flowy_editor/example/assets/document.json deleted file mode 100644 index 307b4bf92f..0000000000 --- a/frontend/app_flowy/packages/flowy_editor/example/assets/document.json +++ /dev/null @@ -1,245 +0,0 @@ -{ - "document": { - "type": "editor", - "attributes": {}, - "children": [ - { - "type": "image", - "attributes": { - "image_src": "https://images.squarespace-cdn.com/content/v1/617f6f16b877c06711e87373/c3f23723-37f4-44d7-9c5d-6e2a53064ae7/Asset+10.png?format=1500w" - } - }, - { - "type": "text", - "delta": [ - { - "insert": "👋 Welcome to AppFlowy!", - "attributes": { - "href": "https://www.appflowy.io/", - "heading": "h1" - } - } - ], - "attributes": { - "heading": "h1" - } - }, - { - "type": "text", - "delta": [ - { "insert": "Here are the basics", "attributes": { "heading": "h2" } } - ], - "attributes": { - "heading": "h2" - } - }, - { - "type": "text", - "delta": [{ "insert": "Click anywhere and just start typing." }], - "attributes": { - "list": "todo", - "todo": false - } - }, - { - "type": "text", - "delta": [{ "insert": "Click anywhere and just start typing." }], - "attributes": { - "list": "bullet" - } - }, - { - "type": "text", - "delta": [{ "insert": "Click anywhere and just start typing." }], - "attributes": { - "list": "bullet" - } - }, - { - "type": "text", - "delta": [ - { - "insert": "Highlight", - "attributes": { "highlight": "0xFFFFFF00" } - }, - { "insert": " Click anywhere and just start typing" }, - { "insert": " any text, and use the menu at the bottom to " }, - { "insert": "style", "attributes": { "italic": true } }, - { "insert": " your ", "attributes": { "bold": true } }, - { "insert": "writing", "attributes": { "underline": true } }, - { - "insert": " however you like.", - "attributes": { "strikethrough": true } - } - ], - "attributes": { - "checkbox": false - } - }, - { - "type": "text", - "delta": [ - { "insert": "Have a question? ", "attributes": { "heading": "h2" } } - ], - "attributes": { - "heading": "h2" - } - }, - { - "type": "text", - "delta": [ - { - "insert": "1. Click the '?' at the bottom right for help and support." - } - ], - "attributes": { - "quotes": true - } - }, - { - "type": "text", - "delta": [ - { - "insert": "2. Click the '?' at the bottom right for help and support." - } - ], - "attributes": {} - }, - { - "type": "text", - "delta": [ - { - "insert": "3. Click the '?' at the bottom right for help and support." - } - ], - "attributes": {} - }, - { - "type": "text", - "delta": [ - { - "insert": "4. Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support." - } - ], - "attributes": {} - }, - { - "type": "text", - "delta": [ - { - "insert": "5. Click the '?' at the bottom right for help and support." - } - ], - "attributes": {} - }, - { - "type": "text", - "delta": [ - { - "insert": "6. Click the '?' at the bottom right for help and support." - } - ], - "attributes": {} - }, - { - "type": "text", - "delta": [ - { - "insert": "7. Click the '?' at the bottom right for help and support." - } - ], - "attributes": {} - }, - { - "type": "text", - "delta": [ - { - "insert": "8. Click the '?' at the bottom right for help and support." - } - ], - "attributes": {} - }, - { - "type": "text", - "delta": [ - { - "insert": "9. Click the '?' at the bottom right for help and support." - } - ], - "attributes": {} - }, - { - "type": "text", - "delta": [ - { - "insert": "10. Click the '?' at the bottom right for help and support." - } - ], - "attributes": {} - }, - { - "type": "text", - "delta": [ - { - "insert": "11. Click the '?' at the bottom right for help and support." - } - ], - "attributes": {} - }, - { - "type": "text", - "delta": [ - { - "insert": "Click the '?' at the bottom right for help and support." - } - ], - "attributes": {} - }, - { - "type": "text", - "delta": [ - { - "insert": "Click the '?' at the bottom right for help and support." - } - ], - "attributes": {} - }, - { - "type": "text", - "delta": [ - { - "insert": "Click the '?' at the bottom right for help and support." - } - ], - "attributes": {} - }, - { - "type": "text", - "delta": [ - { - "insert": "Click the '?' at the bottom right for help and support." - } - ], - "attributes": {} - }, - { - "type": "text", - "delta": [ - { - "insert": "Click the '?' at the bottom right for help and support." - } - ], - "attributes": {} - }, - { - "type": "text", - "delta": [ - { - "insert": "Click the '?' at the bottom right for help and support." - } - ], - "attributes": {} - } - ] - } -} diff --git a/frontend/app_flowy/packages/flowy_editor/example/pubspec.yaml b/frontend/app_flowy/packages/flowy_editor/example/pubspec.yaml index 65f15eae46..a9b374af5c 100644 --- a/frontend/app_flowy/packages/flowy_editor/example/pubspec.yaml +++ b/frontend/app_flowy/packages/flowy_editor/example/pubspec.yaml @@ -3,7 +3,7 @@ description: A new Flutter project. # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: "none" # Remove this line if you wish to publish to pub.dev # The following defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 @@ -30,7 +30,6 @@ dependencies: flutter: sdk: flutter - # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 @@ -58,7 +57,6 @@ dev_dependencies: # The following section is specific to Flutter packages. flutter: - # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. @@ -66,7 +64,6 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - - document.json - example.json - big_document.json # - images/a_dot_ham.jpeg diff --git a/frontend/app_flowy/packages/flowy_editor/pubspec.yaml b/frontend/app_flowy/packages/flowy_editor/pubspec.yaml index 05c87f8e33..24021ec252 100644 --- a/frontend/app_flowy/packages/flowy_editor/pubspec.yaml +++ b/frontend/app_flowy/packages/flowy_editor/pubspec.yaml @@ -31,7 +31,6 @@ flutter: - assets/images/toolbar/ - assets/images/popup_list/ - assets/images/ - - assets/document.json # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # From fe7c19666f873967382e346bc2304a535ab255d2 Mon Sep 17 00:00:00 2001 From: Vincent Chan Date: Mon, 15 Aug 2022 13:58:46 +0800 Subject: [PATCH 4/7] feat: add converter --- .../packages/flowy_editor/lib/src/infra/html_converter.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/src/infra/html_converter.dart b/frontend/app_flowy/packages/flowy_editor/lib/src/infra/html_converter.dart index 5963d5955d..329544d0cc 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/src/infra/html_converter.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/src/infra/html_converter.dart @@ -91,7 +91,7 @@ class HTMLToNodesConverter { for (final child in element.nodes.toList()) { if (child is html.Element) { - result.addAll(_handleElement(child, {"subtype": "quote"})); + result.addAll(_handleElement(child, {"subtype": StyleKey.quote})); } } From e34ff509233e4b933e89ba7e41dc079ebbc4c284 Mon Sep 17 00:00:00 2001 From: Vincent Chan Date: Mon, 15 Aug 2022 14:03:38 +0800 Subject: [PATCH 5/7] fix: CI errors --- frontend/app_flowy/packages/flowy_editor/pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/app_flowy/packages/flowy_editor/pubspec.yaml b/frontend/app_flowy/packages/flowy_editor/pubspec.yaml index 24021ec252..05c87f8e33 100644 --- a/frontend/app_flowy/packages/flowy_editor/pubspec.yaml +++ b/frontend/app_flowy/packages/flowy_editor/pubspec.yaml @@ -31,6 +31,7 @@ flutter: - assets/images/toolbar/ - assets/images/popup_list/ - assets/images/ + - assets/document.json # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # From a3ffbe2f00330a3e4e55eac19a87686869a77955 Mon Sep 17 00:00:00 2001 From: Vincent Chan Date: Mon, 15 Aug 2022 14:21:15 +0800 Subject: [PATCH 6/7] refactor: html text into class --- .../lib/src/infra/html_converter.dart | 142 ++++++++++-------- 1 file changed, 76 insertions(+), 66 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/src/infra/html_converter.dart b/frontend/app_flowy/packages/flowy_editor/lib/src/infra/html_converter.dart index 329544d0cc..59efabe364 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/src/infra/html_converter.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/src/infra/html_converter.dart @@ -1,4 +1,5 @@ import 'dart:collection'; +import 'dart:ui'; import 'package:flowy_editor/src/document/attributes.dart'; import 'package:flowy_editor/src/document/node.dart'; @@ -8,23 +9,35 @@ import 'package:flutter/material.dart'; import 'package:html/parser.dart' show parse; import 'package:html/dom.dart' as html; -const String tagH1 = "h1"; -const String tagH2 = "h2"; -const String tagH3 = "h3"; -const String tagOrderedList = "ol"; -const String tagUnorderedList = "ul"; -const String tagList = "li"; -const String tagParagraph = "p"; -const String tagImage = "img"; -const String tagAnchor = "a"; -const String tagItalic = "i"; -const String tagBold = "b"; -const String tagUnderline = "u"; -const String tagDel = "del"; -const String tagStrong = "strong"; -const String tagSpan = "span"; -const String tagCode = "code"; -const String tagBlockQuote = "blockquote"; +class HTMLTag { + static const h1 = "h1"; + static const h2 = "h2"; + static const h3 = "h3"; + static const orderedList = "ol"; + static const unorderedList = "ul"; + static const list = "li"; + static const paragraph = "p"; + static const image = "img"; + static const anchor = "a"; + static const italic = "i"; + static const bold = "b"; + static const underline = "u"; + static const del = "del"; + static const strong = "strong"; + static const span = "span"; + static const code = "code"; + static const blockQuote = "blockquote"; + static const div = "div"; + + static bool isTopLevel(String tag) { + return tag == h1 || + tag == h2 || + tag == h3 || + tag == paragraph || + tag == div || + tag == blockQuote; + } +} extension on Color { String toRgbaString() { @@ -55,15 +68,15 @@ class HTMLToNodesConverter { final result = []; for (final child in childNodes) { if (child is html.Element) { - if (child.localName == tagAnchor || - child.localName == tagSpan || - child.localName == tagCode || - child.localName == tagStrong || - child.localName == tagUnderline || - child.localName == tagItalic || - child.localName == tagDel) { + if (child.localName == HTMLTag.anchor || + child.localName == HTMLTag.span || + child.localName == HTMLTag.code || + child.localName == HTMLTag.strong || + child.localName == HTMLTag.underline || + child.localName == HTMLTag.italic || + child.localName == HTMLTag.del) { _handleRichTextElement(delta, child); - } else if (child.localName == tagBold) { + } else if (child.localName == HTMLTag.bold) { // Google docs wraps the the content inside the `` tag. // It's strange if (!_inParagraph) { @@ -71,7 +84,7 @@ class HTMLToNodesConverter { } else { result.add(_handleRichText(child)); } - } else if (child.localName == tagBlockQuote) { + } else if (child.localName == HTMLTag.blockQuote) { result.addAll(_handleBlockQuote(child)); } else { result.addAll(_handleElement(child)); @@ -105,19 +118,19 @@ class HTMLToNodesConverter { List _handleElement(html.Element element, [Map? attributes]) { - if (element.localName == tagH1) { - return [_handleHeadingElement(element, tagH1)]; - } else if (element.localName == tagH2) { - return [_handleHeadingElement(element, tagH2)]; - } else if (element.localName == tagH3) { - return [_handleHeadingElement(element, tagH3)]; - } else if (element.localName == tagUnorderedList) { + if (element.localName == HTMLTag.h1) { + return [_handleHeadingElement(element, HTMLTag.h1)]; + } else if (element.localName == HTMLTag.h2) { + return [_handleHeadingElement(element, HTMLTag.h2)]; + } else if (element.localName == HTMLTag.h3) { + return [_handleHeadingElement(element, HTMLTag.h3)]; + } else if (element.localName == HTMLTag.unorderedList) { return _handleUnorderedList(element); - } else if (element.localName == tagOrderedList) { + } else if (element.localName == HTMLTag.orderedList) { return _handleOrderedList(element); - } else if (element.localName == tagList) { + } else if (element.localName == HTMLTag.list) { return _handleListElement(element); - } else if (element.localName == tagParagraph) { + } else if (element.localName == HTMLTag.paragraph) { return [_handleParagraph(element, attributes)]; } else { final delta = Delta(); @@ -236,23 +249,24 @@ class HTMLToNodesConverter { } _handleRichTextElement(Delta delta, html.Element element) { - if (element.localName == tagSpan) { + if (element.localName == HTMLTag.span) { delta.insert(element.text, _getDeltaAttributesFromHtmlAttributes(element.attributes)); - } else if (element.localName == tagAnchor) { + } else if (element.localName == HTMLTag.anchor) { final hyperLink = element.attributes["href"]; Map? attributes; if (hyperLink != null) { attributes = {"href": hyperLink}; } delta.insert(element.text, attributes); - } else if (element.localName == tagStrong || element.localName == tagBold) { + } else if (element.localName == HTMLTag.strong || + element.localName == HTMLTag.bold) { delta.insert(element.text, {StyleKey.bold: true}); - } else if (element.localName == tagUnderline) { + } else if (element.localName == HTMLTag.underline) { delta.insert(element.text, {StyleKey.underline: true}); - } else if (element.localName == tagItalic) { + } else if (element.localName == HTMLTag.italic) { delta.insert(element.text, {StyleKey.italic: true}); - } else if (element.localName == tagDel) { + } else if (element.localName == HTMLTag.del) { delta.insert(element.text, {StyleKey.strikethrough: true}); } else { delta.insert(element.text); @@ -265,7 +279,7 @@ class HTMLToNodesConverter { /// A container contains a will be regarded as a image block Node _handleRichText(html.Element element, [Map? attributes]) { - final image = element.querySelector(tagImage); + final image = element.querySelector(HTMLTag.image); if (image != null) { final imageNode = _handleImage(image); return imageNode; @@ -419,10 +433,10 @@ class NodesToHTMLConverter { } _addElement(TextNode textNode, html.Element element) { - if (element.localName == tagList) { + if (element.localName == HTMLTag.list) { final isNumbered = textNode.attributes["subtype"] == StyleKey.numberList; - _stashListContainer ??= - html.Element.tag(isNumbered ? tagOrderedList : tagUnorderedList); + _stashListContainer ??= html.Element.tag( + isNumbered ? HTMLTag.orderedList : HTMLTag.unorderedList); _stashListContainer?.append(element); } else { if (_stashListContainer != null) { @@ -524,10 +538,10 @@ class NodesToHTMLConverter { } final childNodes = []; - String tagName = tagParagraph; + String tagName = HTMLTag.paragraph; if (subType == StyleKey.bulletedList || subType == StyleKey.numberList) { - tagName = tagList; + tagName = HTMLTag.list; } else if (subType == StyleKey.checkbox) { final node = html.Element.html(''); if (checked != null && checked) { @@ -536,14 +550,14 @@ class NodesToHTMLConverter { childNodes.add(node); } else if (subType == StyleKey.heading) { if (heading == StyleKey.h1) { - tagName = tagH1; + tagName = HTMLTag.h1; } else if (heading == StyleKey.h2) { - tagName = tagH2; + tagName = HTMLTag.h2; } else if (heading == StyleKey.h3) { - tagName = tagH3; + tagName = HTMLTag.h3; } } else if (subType == StyleKey.quote) { - tagName = tagBlockQuote; + tagName = HTMLTag.blockQuote; } for (final op in delta) { @@ -551,26 +565,26 @@ class NodesToHTMLConverter { final attributes = op.attributes; if (attributes != null) { if (attributes.length == 1 && attributes[StyleKey.bold] == true) { - final strong = html.Element.tag(tagStrong); + final strong = html.Element.tag(HTMLTag.strong); strong.append(html.Text(op.content)); childNodes.add(strong); } else if (attributes.length == 1 && attributes[StyleKey.underline] == true) { - final strong = html.Element.tag(tagUnderline); + final strong = html.Element.tag(HTMLTag.underline); strong.append(html.Text(op.content)); childNodes.add(strong); } else if (attributes.length == 1 && attributes[StyleKey.italic] == true) { - final strong = html.Element.tag(tagItalic); + final strong = html.Element.tag(HTMLTag.italic); strong.append(html.Text(op.content)); childNodes.add(strong); } else if (attributes.length == 1 && attributes[StyleKey.strikethrough] == true) { - final strong = html.Element.tag(tagDel); + final strong = html.Element.tag(HTMLTag.del); strong.append(html.Text(op.content)); childNodes.add(strong); } else { - final span = html.Element.tag(tagSpan); + final span = html.Element.tag(HTMLTag.span); final cssString = _attributesToCssStyle(attributes); if (cssString.isNotEmpty) { span.attributes["style"] = cssString; @@ -584,24 +598,20 @@ class NodesToHTMLConverter { } } - if (tagName == tagBlockQuote) { - final p = html.Element.tag(tagParagraph); + if (tagName == HTMLTag.blockQuote) { + final p = html.Element.tag(HTMLTag.paragraph); for (final node in childNodes) { p.append(node); } final blockQuote = html.Element.tag(tagName); blockQuote.append(p); return blockQuote; - } else if (tagName != tagParagraph && - tagName != tagH1 && - tagName != tagH2 && - tagName != tagH3 && - tagName != tagBlockQuote) { - final p = html.Element.tag(tagParagraph); + } else if (!HTMLTag.isTopLevel(tagName)) { + final p = html.Element.tag(HTMLTag.paragraph); for (final node in childNodes) { p.append(node); } - final result = html.Element.tag(tagList); + final result = html.Element.tag(HTMLTag.list); result.append(p); return result; } else { From e826006fa704ef523712518b9c3d3938e4d127d0 Mon Sep 17 00:00:00 2001 From: Vincent Chan Date: Mon, 15 Aug 2022 14:52:29 +0800 Subject: [PATCH 7/7] refactor: color extensions --- .../lib/src/extensions/color_extension.dart | 36 ++++++++++++++++ .../lib/src/infra/html_converter.dart | 43 ++----------------- 2 files changed, 40 insertions(+), 39 deletions(-) create mode 100644 frontend/app_flowy/packages/flowy_editor/lib/src/extensions/color_extension.dart diff --git a/frontend/app_flowy/packages/flowy_editor/lib/src/extensions/color_extension.dart b/frontend/app_flowy/packages/flowy_editor/lib/src/extensions/color_extension.dart new file mode 100644 index 0000000000..7228127104 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_editor/lib/src/extensions/color_extension.dart @@ -0,0 +1,36 @@ +import 'package:flutter/painting.dart'; + +extension ColorExtension on Color { + /// Try to parse the `rgba(red, greed, blue, alpha)` + /// from the string. + static Color? tryFromRgbaString(String colorString) { + final reg = RegExp(r'rgba\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)'); + final match = reg.firstMatch(colorString); + if (match == null) { + return null; + } + + if (match.groupCount < 4) { + return null; + } + final redStr = match.group(1); + final greenStr = match.group(2); + final blueStr = match.group(3); + final alphaStr = match.group(4); + + final red = redStr != null ? int.tryParse(redStr) : null; + final green = greenStr != null ? int.tryParse(greenStr) : null; + final blue = blueStr != null ? int.tryParse(blueStr) : null; + final alpha = alphaStr != null ? int.tryParse(alphaStr) : null; + + if (red == null || green == null || blue == null || alpha == null) { + return null; + } + + return Color.fromARGB(alpha, red, green, blue); + } + + String toRgbaString() { + return 'rgba($red, $green, $blue, $alpha)'; + } +} diff --git a/frontend/app_flowy/packages/flowy_editor/lib/src/infra/html_converter.dart b/frontend/app_flowy/packages/flowy_editor/lib/src/infra/html_converter.dart index 59efabe364..0aa8d4fd13 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/src/infra/html_converter.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/src/infra/html_converter.dart @@ -5,6 +5,7 @@ import 'package:flowy_editor/src/document/attributes.dart'; import 'package:flowy_editor/src/document/node.dart'; import 'package:flowy_editor/src/document/text_delta.dart'; import 'package:flowy_editor/src/render/rich_text/rich_text_style.dart'; +import 'package:flowy_editor/src/extensions/color_extension.dart'; import 'package:flutter/material.dart'; import 'package:html/parser.dart' show parse; import 'package:html/dom.dart' as html; @@ -39,12 +40,6 @@ class HTMLTag { } } -extension on Color { - String toRgbaString() { - return 'rgba($red, $green, $blue, $alpha)'; - } -} - /// Converting the HTML to nodes class HTMLToNodesConverter { final html.Document _document; @@ -192,7 +187,9 @@ class HTMLToNodesConverter { } final backgroundColorStr = cssMap["background-color"]; - final backgroundColor = _tryParseCssColorString(backgroundColorStr); + final backgroundColor = backgroundColorStr == null + ? null + : ColorExtension.tryFromRgbaString(backgroundColorStr); if (backgroundColor != null) { attrs[StyleKey.backgroundColor] = '0x${backgroundColor.value.toRadixString(16)}'; @@ -216,38 +213,6 @@ class HTMLToNodesConverter { } } - /// Try to parse the `rgba(red, greed, blue, alpha)` - /// from the string. - Color? _tryParseCssColorString(String? colorString) { - if (colorString == null) { - return null; - } - final reg = RegExp(r'rgba\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)'); - final match = reg.firstMatch(colorString); - if (match == null) { - return null; - } - - if (match.groupCount < 4) { - return null; - } - final redStr = match.group(1); - final greenStr = match.group(2); - final blueStr = match.group(3); - final alphaStr = match.group(4); - - final red = redStr != null ? int.tryParse(redStr) : null; - final green = greenStr != null ? int.tryParse(greenStr) : null; - final blue = blueStr != null ? int.tryParse(blueStr) : null; - final alpha = alphaStr != null ? int.tryParse(alphaStr) : null; - - if (red == null || green == null || blue == null || alpha == null) { - return null; - } - - return Color.fromARGB(alpha, red, green, blue); - } - _handleRichTextElement(Delta delta, html.Element element) { if (element.localName == HTMLTag.span) { delta.insert(element.text,