mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #849 from AppFlowy-IO/feat/copy-styles
Feat: copy h1/h2/h3 styles
This commit is contained in:
commit
a23113e48d
@ -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": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,7 +3,7 @@ description: A new Flutter project.
|
|||||||
|
|
||||||
# The following line prevents the package from being accidentally published to
|
# The following line prevents the package from being accidentally published to
|
||||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
# 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.
|
# The following defines the version and build number for your application.
|
||||||
# A version number is three numbers separated by dots, like 1.2.43
|
# A version number is three numbers separated by dots, like 1.2.43
|
||||||
@ -30,7 +30,6 @@ dependencies:
|
|||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.2
|
cupertino_icons: ^1.0.2
|
||||||
@ -58,7 +57,6 @@ dev_dependencies:
|
|||||||
|
|
||||||
# The following section is specific to Flutter packages.
|
# The following section is specific to Flutter packages.
|
||||||
flutter:
|
flutter:
|
||||||
|
|
||||||
# The following line ensures that the Material Icons font is
|
# The following line ensures that the Material Icons font is
|
||||||
# included with your application, so that you can use the icons in
|
# included with your application, so that you can use the icons in
|
||||||
# the material Icons class.
|
# the material Icons class.
|
||||||
@ -66,7 +64,6 @@ flutter:
|
|||||||
|
|
||||||
# To add assets to your application, add an assets section, like this:
|
# To add assets to your application, add an assets section, like this:
|
||||||
assets:
|
assets:
|
||||||
- document.json
|
|
||||||
- example.json
|
- example.json
|
||||||
- big_document.json
|
- big_document.json
|
||||||
# - images/a_dot_ham.jpeg
|
# - images/a_dot_ham.jpeg
|
||||||
|
@ -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)';
|
||||||
|
}
|
||||||
|
}
|
@ -1,33 +1,42 @@
|
|||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flowy_editor/src/document/attributes.dart';
|
import 'package:flowy_editor/src/document/attributes.dart';
|
||||||
import 'package:flowy_editor/src/document/node.dart';
|
import 'package:flowy_editor/src/document/node.dart';
|
||||||
import 'package:flowy_editor/src/document/text_delta.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/render/rich_text/rich_text_style.dart';
|
||||||
|
import 'package:flowy_editor/src/extensions/color_extension.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:html/parser.dart' show parse;
|
import 'package:html/parser.dart' show parse;
|
||||||
import 'package:html/dom.dart' as html;
|
import 'package:html/dom.dart' as html;
|
||||||
|
|
||||||
const String tagH1 = "h1";
|
class HTMLTag {
|
||||||
const String tagH2 = "h2";
|
static const h1 = "h1";
|
||||||
const String tagH3 = "h3";
|
static const h2 = "h2";
|
||||||
const String tagOrderedList = "ol";
|
static const h3 = "h3";
|
||||||
const String tagUnorderedList = "ul";
|
static const orderedList = "ol";
|
||||||
const String tagList = "li";
|
static const unorderedList = "ul";
|
||||||
const String tagParagraph = "p";
|
static const list = "li";
|
||||||
const String tagImage = "img";
|
static const paragraph = "p";
|
||||||
const String tagAnchor = "a";
|
static const image = "img";
|
||||||
const String tagItalic = "i";
|
static const anchor = "a";
|
||||||
const String tagBold = "b";
|
static const italic = "i";
|
||||||
const String tagUnderline = "u";
|
static const bold = "b";
|
||||||
const String tagDel = "del";
|
static const underline = "u";
|
||||||
const String tagStrong = "strong";
|
static const del = "del";
|
||||||
const String tagSpan = "span";
|
static const strong = "strong";
|
||||||
const String tagCode = "code";
|
static const span = "span";
|
||||||
|
static const code = "code";
|
||||||
|
static const blockQuote = "blockquote";
|
||||||
|
static const div = "div";
|
||||||
|
|
||||||
extension on Color {
|
static bool isTopLevel(String tag) {
|
||||||
String toRgbaString() {
|
return tag == h1 ||
|
||||||
return 'rgba($red, $green, $blue, $alpha)';
|
tag == h2 ||
|
||||||
|
tag == h3 ||
|
||||||
|
tag == paragraph ||
|
||||||
|
tag == div ||
|
||||||
|
tag == blockQuote;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,15 +63,15 @@ class HTMLToNodesConverter {
|
|||||||
final result = <Node>[];
|
final result = <Node>[];
|
||||||
for (final child in childNodes) {
|
for (final child in childNodes) {
|
||||||
if (child is html.Element) {
|
if (child is html.Element) {
|
||||||
if (child.localName == tagAnchor ||
|
if (child.localName == HTMLTag.anchor ||
|
||||||
child.localName == tagSpan ||
|
child.localName == HTMLTag.span ||
|
||||||
child.localName == tagCode ||
|
child.localName == HTMLTag.code ||
|
||||||
child.localName == tagStrong ||
|
child.localName == HTMLTag.strong ||
|
||||||
child.localName == tagUnderline ||
|
child.localName == HTMLTag.underline ||
|
||||||
child.localName == tagItalic ||
|
child.localName == HTMLTag.italic ||
|
||||||
child.localName == tagDel) {
|
child.localName == HTMLTag.del) {
|
||||||
_handleRichTextElement(delta, child);
|
_handleRichTextElement(delta, child);
|
||||||
} else if (child.localName == tagBold) {
|
} else if (child.localName == HTMLTag.bold) {
|
||||||
// Google docs wraps the the content inside the `<b></b>` tag.
|
// Google docs wraps the the content inside the `<b></b>` tag.
|
||||||
// It's strange
|
// It's strange
|
||||||
if (!_inParagraph) {
|
if (!_inParagraph) {
|
||||||
@ -70,6 +79,8 @@ class HTMLToNodesConverter {
|
|||||||
} else {
|
} else {
|
||||||
result.add(_handleRichText(child));
|
result.add(_handleRichText(child));
|
||||||
}
|
}
|
||||||
|
} else if (child.localName == HTMLTag.blockQuote) {
|
||||||
|
result.addAll(_handleBlockQuote(child));
|
||||||
} else {
|
} else {
|
||||||
result.addAll(_handleElement(child));
|
result.addAll(_handleElement(child));
|
||||||
}
|
}
|
||||||
@ -83,6 +94,18 @@ class HTMLToNodesConverter {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Node> _handleBlockQuote(html.Element element) {
|
||||||
|
final result = <Node>[];
|
||||||
|
|
||||||
|
for (final child in element.nodes.toList()) {
|
||||||
|
if (child is html.Element) {
|
||||||
|
result.addAll(_handleElement(child, {"subtype": StyleKey.quote}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
List<Node> _handleBTag(html.Element element) {
|
List<Node> _handleBTag(html.Element element) {
|
||||||
final childNodes = element.nodes;
|
final childNodes = element.nodes;
|
||||||
return _handleContainer(childNodes);
|
return _handleContainer(childNodes);
|
||||||
@ -90,19 +113,19 @@ class HTMLToNodesConverter {
|
|||||||
|
|
||||||
List<Node> _handleElement(html.Element element,
|
List<Node> _handleElement(html.Element element,
|
||||||
[Map<String, dynamic>? attributes]) {
|
[Map<String, dynamic>? attributes]) {
|
||||||
if (element.localName == tagH1) {
|
if (element.localName == HTMLTag.h1) {
|
||||||
return [_handleHeadingElement(element, tagH1)];
|
return [_handleHeadingElement(element, HTMLTag.h1)];
|
||||||
} else if (element.localName == tagH2) {
|
} else if (element.localName == HTMLTag.h2) {
|
||||||
return [_handleHeadingElement(element, tagH2)];
|
return [_handleHeadingElement(element, HTMLTag.h2)];
|
||||||
} else if (element.localName == tagH3) {
|
} else if (element.localName == HTMLTag.h3) {
|
||||||
return [_handleHeadingElement(element, tagH3)];
|
return [_handleHeadingElement(element, HTMLTag.h3)];
|
||||||
} else if (element.localName == tagUnorderedList) {
|
} else if (element.localName == HTMLTag.unorderedList) {
|
||||||
return _handleUnorderedList(element);
|
return _handleUnorderedList(element);
|
||||||
} else if (element.localName == tagOrderedList) {
|
} else if (element.localName == HTMLTag.orderedList) {
|
||||||
return _handleOrderedList(element);
|
return _handleOrderedList(element);
|
||||||
} else if (element.localName == tagList) {
|
} else if (element.localName == HTMLTag.list) {
|
||||||
return _handleListElement(element);
|
return _handleListElement(element);
|
||||||
} else if (element.localName == tagParagraph) {
|
} else if (element.localName == HTMLTag.paragraph) {
|
||||||
return [_handleParagraph(element, attributes)];
|
return [_handleParagraph(element, attributes)];
|
||||||
} else {
|
} else {
|
||||||
final delta = Delta();
|
final delta = Delta();
|
||||||
@ -164,7 +187,9 @@ class HTMLToNodesConverter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final backgroundColorStr = cssMap["background-color"];
|
final backgroundColorStr = cssMap["background-color"];
|
||||||
final backgroundColor = _tryParseCssColorString(backgroundColorStr);
|
final backgroundColor = backgroundColorStr == null
|
||||||
|
? null
|
||||||
|
: ColorExtension.tryFromRgbaString(backgroundColorStr);
|
||||||
if (backgroundColor != null) {
|
if (backgroundColor != null) {
|
||||||
attrs[StyleKey.backgroundColor] =
|
attrs[StyleKey.backgroundColor] =
|
||||||
'0x${backgroundColor.value.toRadixString(16)}';
|
'0x${backgroundColor.value.toRadixString(16)}';
|
||||||
@ -188,56 +213,25 @@ 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) {
|
_handleRichTextElement(Delta delta, html.Element element) {
|
||||||
if (element.localName == tagSpan) {
|
if (element.localName == HTMLTag.span) {
|
||||||
delta.insert(element.text,
|
delta.insert(element.text,
|
||||||
_getDeltaAttributesFromHtmlAttributes(element.attributes));
|
_getDeltaAttributesFromHtmlAttributes(element.attributes));
|
||||||
} else if (element.localName == tagAnchor) {
|
} else if (element.localName == HTMLTag.anchor) {
|
||||||
final hyperLink = element.attributes["href"];
|
final hyperLink = element.attributes["href"];
|
||||||
Map<String, dynamic>? attributes;
|
Map<String, dynamic>? attributes;
|
||||||
if (hyperLink != null) {
|
if (hyperLink != null) {
|
||||||
attributes = {"href": hyperLink};
|
attributes = {"href": hyperLink};
|
||||||
}
|
}
|
||||||
delta.insert(element.text, attributes);
|
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});
|
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});
|
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});
|
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});
|
delta.insert(element.text, {StyleKey.strikethrough: true});
|
||||||
} else {
|
} else {
|
||||||
delta.insert(element.text);
|
delta.insert(element.text);
|
||||||
@ -250,7 +244,7 @@ class HTMLToNodesConverter {
|
|||||||
/// A container contains a <img /> will be regarded as a image block
|
/// A container contains a <img /> will be regarded as a image block
|
||||||
Node _handleRichText(html.Element element,
|
Node _handleRichText(html.Element element,
|
||||||
[Map<String, dynamic>? attributes]) {
|
[Map<String, dynamic>? attributes]) {
|
||||||
final image = element.querySelector(tagImage);
|
final image = element.querySelector(HTMLTag.image);
|
||||||
if (image != null) {
|
if (image != null) {
|
||||||
final imageNode = _handleImage(image);
|
final imageNode = _handleImage(image);
|
||||||
return imageNode;
|
return imageNode;
|
||||||
@ -404,10 +398,10 @@ class NodesToHTMLConverter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_addElement(TextNode textNode, html.Element element) {
|
_addElement(TextNode textNode, html.Element element) {
|
||||||
if (element.localName == tagList) {
|
if (element.localName == HTMLTag.list) {
|
||||||
final isNumbered = textNode.attributes["subtype"] == StyleKey.numberList;
|
final isNumbered = textNode.attributes["subtype"] == StyleKey.numberList;
|
||||||
_stashListContainer ??=
|
_stashListContainer ??= html.Element.tag(
|
||||||
html.Element.tag(isNumbered ? tagOrderedList : tagUnorderedList);
|
isNumbered ? HTMLTag.orderedList : HTMLTag.unorderedList);
|
||||||
_stashListContainer?.append(element);
|
_stashListContainer?.append(element);
|
||||||
} else {
|
} else {
|
||||||
if (_stashListContainer != null) {
|
if (_stashListContainer != null) {
|
||||||
@ -427,8 +421,10 @@ class NodesToHTMLConverter {
|
|||||||
|
|
||||||
html.Element _textNodeToHtml(TextNode textNode, {int? end}) {
|
html.Element _textNodeToHtml(TextNode textNode, {int? end}) {
|
||||||
String? subType = textNode.attributes["subtype"];
|
String? subType = textNode.attributes["subtype"];
|
||||||
|
String? heading = textNode.attributes["heading"];
|
||||||
return _deltaToHtml(textNode.delta,
|
return _deltaToHtml(textNode.delta,
|
||||||
subType: subType,
|
subType: subType,
|
||||||
|
heading: heading,
|
||||||
end: end,
|
end: end,
|
||||||
checked: textNode.attributes["checkbox"] == true);
|
checked: textNode.attributes["checkbox"] == true);
|
||||||
}
|
}
|
||||||
@ -501,22 +497,32 @@ class NodesToHTMLConverter {
|
|||||||
/// <span style="...">Text</span>
|
/// <span style="...">Text</span>
|
||||||
/// ```
|
/// ```
|
||||||
html.Element _deltaToHtml(Delta delta,
|
html.Element _deltaToHtml(Delta delta,
|
||||||
{String? subType, int? end, bool? checked}) {
|
{String? subType, String? heading, int? end, bool? checked}) {
|
||||||
if (end != null) {
|
if (end != null) {
|
||||||
delta = delta.slice(0, end);
|
delta = delta.slice(0, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
final childNodes = <html.Node>[];
|
final childNodes = <html.Node>[];
|
||||||
String tagName = tagParagraph;
|
String tagName = HTMLTag.paragraph;
|
||||||
|
|
||||||
if (subType == StyleKey.bulletedList || subType == StyleKey.numberList) {
|
if (subType == StyleKey.bulletedList || subType == StyleKey.numberList) {
|
||||||
tagName = tagList;
|
tagName = HTMLTag.list;
|
||||||
} else if (subType == StyleKey.checkbox) {
|
} else if (subType == StyleKey.checkbox) {
|
||||||
final node = html.Element.html('<input type="checkbox" />');
|
final node = html.Element.html('<input type="checkbox" />');
|
||||||
if (checked != null && checked) {
|
if (checked != null && checked) {
|
||||||
node.attributes["checked"] = "true";
|
node.attributes["checked"] = "true";
|
||||||
}
|
}
|
||||||
childNodes.add(node);
|
childNodes.add(node);
|
||||||
|
} else if (subType == StyleKey.heading) {
|
||||||
|
if (heading == StyleKey.h1) {
|
||||||
|
tagName = HTMLTag.h1;
|
||||||
|
} else if (heading == StyleKey.h2) {
|
||||||
|
tagName = HTMLTag.h2;
|
||||||
|
} else if (heading == StyleKey.h3) {
|
||||||
|
tagName = HTMLTag.h3;
|
||||||
|
}
|
||||||
|
} else if (subType == StyleKey.quote) {
|
||||||
|
tagName = HTMLTag.blockQuote;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final op in delta) {
|
for (final op in delta) {
|
||||||
@ -524,26 +530,26 @@ class NodesToHTMLConverter {
|
|||||||
final attributes = op.attributes;
|
final attributes = op.attributes;
|
||||||
if (attributes != null) {
|
if (attributes != null) {
|
||||||
if (attributes.length == 1 && attributes[StyleKey.bold] == true) {
|
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));
|
strong.append(html.Text(op.content));
|
||||||
childNodes.add(strong);
|
childNodes.add(strong);
|
||||||
} else if (attributes.length == 1 &&
|
} else if (attributes.length == 1 &&
|
||||||
attributes[StyleKey.underline] == true) {
|
attributes[StyleKey.underline] == true) {
|
||||||
final strong = html.Element.tag(tagUnderline);
|
final strong = html.Element.tag(HTMLTag.underline);
|
||||||
strong.append(html.Text(op.content));
|
strong.append(html.Text(op.content));
|
||||||
childNodes.add(strong);
|
childNodes.add(strong);
|
||||||
} else if (attributes.length == 1 &&
|
} else if (attributes.length == 1 &&
|
||||||
attributes[StyleKey.italic] == true) {
|
attributes[StyleKey.italic] == true) {
|
||||||
final strong = html.Element.tag(tagItalic);
|
final strong = html.Element.tag(HTMLTag.italic);
|
||||||
strong.append(html.Text(op.content));
|
strong.append(html.Text(op.content));
|
||||||
childNodes.add(strong);
|
childNodes.add(strong);
|
||||||
} else if (attributes.length == 1 &&
|
} else if (attributes.length == 1 &&
|
||||||
attributes[StyleKey.strikethrough] == true) {
|
attributes[StyleKey.strikethrough] == true) {
|
||||||
final strong = html.Element.tag(tagDel);
|
final strong = html.Element.tag(HTMLTag.del);
|
||||||
strong.append(html.Text(op.content));
|
strong.append(html.Text(op.content));
|
||||||
childNodes.add(strong);
|
childNodes.add(strong);
|
||||||
} else {
|
} else {
|
||||||
final span = html.Element.tag(tagSpan);
|
final span = html.Element.tag(HTMLTag.span);
|
||||||
final cssString = _attributesToCssStyle(attributes);
|
final cssString = _attributesToCssStyle(attributes);
|
||||||
if (cssString.isNotEmpty) {
|
if (cssString.isNotEmpty) {
|
||||||
span.attributes["style"] = cssString;
|
span.attributes["style"] = cssString;
|
||||||
@ -557,12 +563,20 @@ class NodesToHTMLConverter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tagName != tagParagraph) {
|
if (tagName == HTMLTag.blockQuote) {
|
||||||
final p = html.Element.tag(tagParagraph);
|
final p = html.Element.tag(HTMLTag.paragraph);
|
||||||
for (final node in childNodes) {
|
for (final node in childNodes) {
|
||||||
p.append(node);
|
p.append(node);
|
||||||
}
|
}
|
||||||
final result = html.Element.tag(tagList);
|
final blockQuote = html.Element.tag(tagName);
|
||||||
|
blockQuote.append(p);
|
||||||
|
return blockQuote;
|
||||||
|
} 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(HTMLTag.list);
|
||||||
result.append(p);
|
result.append(p);
|
||||||
return result;
|
return result;
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user