Skip to content

Commit

Permalink
fix: nested elements getting ignored
Browse files Browse the repository at this point in the history
  • Loading branch information
alihassan143 committed Jun 9, 2023
1 parent 6677e2a commit 3847701
Show file tree
Hide file tree
Showing 6 changed files with 665 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ Node headingNode({
required int level,
Delta? delta,
Attributes? attributes,
Iterable<Node>? children,
}) {
attributes ??= {'delta': (delta ?? Delta()).toJson()};
return Node(
type: HeadingBlockKeys.type,
children: children ?? [],
attributes: {
HeadingBlockKeys.level: level,
...attributes,
Expand Down
52 changes: 37 additions & 15 deletions lib/src/plugins/html/html_document_decoder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,21 @@ class DocumentHTMLDecoder extends Converter<String, Document> {
)
];
case HTMLTags.paragraph:
return [_parseParagraphElement(element)];
return _parseParagraphElement(element);
case HTMLTags.blockQuote:
return _parseBlockQuoteElement(element);
case HTMLTags.image:
return [_parseImageElement(element)];
default:
return [paragraphNode(text: element.text)];
return _parseParagraphElement(element);
}
}

Attributes _parserFormattingElementAttributes(dom.Element element) {
Attributes _parserFormattingElementAttributes(
dom.Element element,
) {
final localName = element.localName;

Attributes attributes = {};
switch (localName) {
case HTMLTags.bold || HTMLTags.strong:
Expand Down Expand Up @@ -133,17 +136,23 @@ class DocumentHTMLDecoder extends Converter<String, Document> {
dom.Element element, {
required int level,
}) {
final delta = _parseDeltaElement(element);
final (delta, specialNodes) = _parseDeltaElement(element);
return headingNode(
level: level,
children: specialNodes,
delta: delta,
);
}

Iterable<Node> _parseBlockQuoteElement(dom.Element element) {
return element.children
.map((child) => _parseListElement(child, type: QuoteBlockKeys.type))
.toList();
final (delta, nodes) = _parseDeltaElement(element);
return [
Node(
type: QuoteBlockKeys.type,
children: nodes,
attributes: {ParagraphBlockKeys.delta: delta.toJson()},
)
];
}

Iterable<Node> _parseUnOrderListElement(dom.Element element) {
Expand All @@ -166,16 +175,17 @@ class DocumentHTMLDecoder extends Converter<String, Document> {
dom.Element element, {
required String type,
}) {
final delta = _parseDeltaElement(element);
final (delta, node) = _parseDeltaElement(element);
return Node(
type: type != ParagraphBlockKeys.type ? type : BulletedListBlockKeys.type,
children: node,
attributes: {ParagraphBlockKeys.delta: delta.toJson()},
);
}

Node _parseParagraphElement(dom.Element element) {
final delta = _parseDeltaElement(element);
return paragraphNode(delta: delta);
Iterable<Node> _parseParagraphElement(dom.Element element) {
final (delta, specialNodes) = _parseDeltaElement(element);
return [paragraphNode(delta: delta), ...specialNodes];
}

Node _parseImageElement(dom.Element element) {
Expand All @@ -185,18 +195,30 @@ class DocumentHTMLDecoder extends Converter<String, Document> {
);
}

Delta _parseDeltaElement(dom.Element element) {
(Delta, Iterable<Node>) _parseDeltaElement(dom.Element element) {
final delta = Delta();
final nodes = <Node>[];
final children = element.nodes.toList();
for (final child in children) {
if (child is dom.Element) {
final attributes = _parserFormattingElementAttributes(child);
delta.insert(child.text, attributes: attributes);
if (child.children.isNotEmpty) {
for (final seocondChild in child.children) {
nodes.addAll(
_parseSpecialElements(
seocondChild,
type: ParagraphBlockKeys.type,
),
);
}
} else {
final attributes = _parserFormattingElementAttributes(child);
delta.insert(child.text, attributes: attributes);
}
} else {
delta.insert(child.text ?? '');
}
}
return delta;
return (delta, nodes);
}

Attributes? _getDeltaAttributesFromHTMLAttributes(
Expand Down
30 changes: 24 additions & 6 deletions lib/src/plugins/html/html_document_encoder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class DocumentHTMLEncoder extends Converter<Document, String> {
return _deltaToHtml(
Delta.fromJson(documentNode.attributes[ParagraphBlockKeys.delta]),
type: documentNode.type,
children: documentNode.children,
attributes: documentNode.attributes,
);
}
Expand Down Expand Up @@ -129,8 +130,10 @@ class DocumentHTMLEncoder extends Converter<Document, String> {
Delta delta, {
required String type,
required Attributes attributes,
required Iterable<Node> children,
}) {
final childNodes = <dom.Node>[];

String tagName = HTMLTags.paragraph;

if (type == BulletedListBlockKeys.type ||
Expand Down Expand Up @@ -176,17 +179,32 @@ class DocumentHTMLEncoder extends Converter<Document, String> {
}
}
}
if (children.isNotEmpty) {
for (var node in children) {
if (node.type != ImageBlockKeys.type) {
childNodes.add(
_deltaToHtml(
node.attributes[ParagraphBlockKeys.delta],
type: node.type,
attributes: node.attributes,
children: node.children,
),
);
} else {
final anchor = dom.Element.tag(HTMLTags.image);
anchor.attributes["src"] = node.attributes[ImageBlockKeys.url];

childNodes.add(_insertText(HTMLTag.span, childNodes: [anchor]));
}
}
}

if (tagName == HTMLTags.blockQuote) {
final blockQuote = dom.Element.tag(tagName);
blockQuote.append(_insertText(HTMLTag.paragraph, childNodes: childNodes));
return blockQuote;
return _insertText(HTMLTag.blockQuote, childNodes: childNodes);
} else if (tagName == HTMLTags.checkbox) {
return _insertText(HTMLTag.div, childNodes: childNodes);
} else if (!HTMLTags.isTopLevel(tagName)) {
final result = dom.Element.tag(HTMLTags.list);
result.append(_insertText(HTMLTag.paragraph, childNodes: childNodes));
return result;
return _insertText(HTMLTag.list, childNodes: childNodes);
} else {
return _insertText(tagName, childNodes: childNodes);
}
Expand Down
199 changes: 198 additions & 1 deletion test/plugins/html/decoder/document_html_decoder_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,16 @@ void main() async {

expect(result.toJson(), example);
});
test('nested parser document', () async {
final result = DocumentHTMLDecoder().convert(nestedhtml);

expect(result.toJson(), nestedDelta);
});
});
}

const rawHTML =
'''<h1>AppFlowyEditor</h1><h2>👋 <strong>Welcome to</strong> <span style="font-weight: bold; font-style: italic">AppFlowy Editor</span></h2><p>AppFlowy Editor is a <strong>highly customizable</strong> <i>rich-text editor</i></p><p> <u>Here</u> is an example <del>your</del> you can give a try</p><p> <span style="font-weight: bold; font-style: italic">Span element</span></p><p> <u>Span element two</u></p><p> <span style="font-weight: bold; text-decoration: line-through">Span element three</span></p><p> <a href="https://appflowy.io">This is an anchor tag!</a></p><h3>Features!</h3><ul><li><p>[x] Customizable</p></li><li><p>[x] Test-covered</p></li><li><p>[ ] more to come!</p></li><li><p>First item</p></li><li><p>Second item</p></li><li><p>List element</p></li></ul><blockquote><p>This is a quote!</p></blockquote><p><code> Code block</code></p><p> <i>Italic one</i></p><p> <i>Italic two</i></p><p> <strong>Bold tag</strong></p><p>You can also use <span style="font-weight: bold; font-style: italic">AppFlowy Editor</span> as a component to build your own app. </p><h3>Awesome features</h3><p>If you have questions or feedback, please submit an issue on Github or join the community along with 1000+ builders!</p><p></p><p></p>''';
'''<h1>AppFlowyEditor</h1><h2>👋 <strong>Welcome to</strong> <span style="font-weight: bold; font-style: italic">AppFlowy Editor</span></h2><p>AppFlowy Editor is a <strong>highly customizable</strong> <i>rich-text editor</i></p><p> <u>Here</u> is an example <del>your</del> you can give a try</p><p> <span style="font-weight: bold; font-style: italic">Span element</span></p><p> <u>Span element two</u></p><p> <span style="font-weight: bold; text-decoration: line-through">Span element three</span></p><p> <a href="https://appflowy.io">This is an anchor tag!</a></p><h3>Features!</h3><ul><li>[x] Customizable</li><li>[x] Test-covered</li><li>[ ] more to come!</li><li>First item</li><li>Second item</li><li>List element</li></ul><blockquote>This is a quote!</blockquote><p><code> Code block</code></p><p> <i>Italic one</i></p><p> <i>Italic two</i></p><p> <strong>Bold tag</strong></p><p>You can also use <span style="font-weight: bold; font-style: italic">AppFlowy Editor</span> as a component to build your own app. </p><h3>Awesome features</h3><p>If you have questions or feedback, please submit an issue on Github or join the community along with 1000+ builders!</p><p></p><p></p>''';

const example = {
'document': {
Expand Down Expand Up @@ -289,3 +294,195 @@ const example = {
]
}
};
const nestedhtml =
'''<h1>Welcome to the playground</h1><blockquote>In case you were wondering what the black box at the bottom is – it\'s the debug view, showing the current state of the editor. You can disable it by pressing on the settings control in the bottom-left of your screen and toggling the debug view setting. The playground is a demo environment built with <code>@lexical/react</code>. Try typing in <strong>some text</strong> with <i>different</i> formats.</blockquote><p>\t</p><img src="https://richtexteditor.com/images/editor-image.png"><p>Make sure to check out the various plugins in the toolbar. You can also use #hashtags or @-mentions too!</p><p></p><p>If you\'d like to find out more about Lexical, you can:</p><ul><li>Visit the <a href="https://lexical.dev/">Lexical website</a> for documentation and more information.</li><li>\t<span><img src="https://richtexteditor.com/images/editor-image.png"></span></li><li>Check out the code on our <a href="https://github.com/facebook/lexical">GitHub repository</a>.</li><li>Playground code can be found <a href="https://github.com/facebook/lexical/tree/main/packages/lexical-playground">here</a>.</li><li>Join our <a href="https://discord.com/invite/KmG4wQnnD9">Discord Server</a> and chat with the team.</li><li>Playground code can be found <a href="https://github.com/facebook/lexical/tree/main/packages/lexical-playground">here</a>.</li></ul><p>Lastly, we\'re constantly adding cool new features to this playground. So make sure you check back here when you next get a chance 🙂.</p><p></p>''';
const nestedDelta = {
'document': {
'type': 'page',
'children': [
{
'type': 'heading',
'data': {
'level': 1,
'delta': [
{'insert': 'Welcome to the playground'}
]
}
},
{
'type': 'quote',
'data': {
'delta': [
{
'insert':
'In case you were wondering what the black box at the bottom is – it\'s the debug view, showing the current state of the editor. You can disable it by pressing on the settings control in the bottom-left of your screen and toggling the debug view setting. The playground is a demo environment built with '
},
{
'insert': '@lexical/react',
'attributes': {'code': true}
},
{'insert': '. Try typing in '},
{
'insert': 'some text',
'attributes': {'bold': true}
},
{'insert': ' with '},
{
'insert': 'different',
'attributes': {'italic': true}
},
{'insert': ' formats.'}
]
}
},
{
'type': 'paragraph',
'data': {
'delta': [
{'insert': '\t'}
]
}
},
{
'type': 'image',
'data': {
'url': 'https://richtexteditor.com/images/editor-image.png',
'align': 'center',
'height': null,
'width': null
}
},
{
'type': 'paragraph',
'data': {
'delta': [
{
'insert':
'Make sure to check out the various plugins in the toolbar. You can also use #hashtags or @-mentions too!'
}
]
}
},
{
'type': 'paragraph',
'data': {'delta': []}
},
{
'type': 'paragraph',
'data': {
'delta': [
{
'insert':
'If you\'d like to find out more about Lexical, you can:'
}
]
}
},
{
'type': 'bulleted_list',
'data': {
'delta': [
{'insert': 'Visit the '},
{
'insert': 'Lexical website',
'attributes': {'href': 'https://lexical.dev/'}
},
{'insert': ' for documentation and more information.'}
]
}
},
{
'type': 'bulleted_list',
'children': [
{
'type': 'image',
'data': {
'url': 'https://richtexteditor.com/images/editor-image.png',
'align': 'center',
'height': null,
'width': null
}
}
],
'data': {
'delta': [
{'insert': '\t'}
]
}
},
{
'type': 'bulleted_list',
'data': {
'delta': [
{'insert': 'Check out the code on our '},
{
'insert': 'GitHub repository',
'attributes': {'href': 'https://github.com/facebook/lexical'}
},
{'insert': '.'}
]
}
},
{
'type': 'bulleted_list',
'data': {
'delta': [
{'insert': 'Playground code can be found '},
{
'insert': 'here',
'attributes': {
'href':
'https://github.com/facebook/lexical/tree/main/packages/lexical-playground'
}
},
{'insert': '.'}
]
}
},
{
'type': 'bulleted_list',
'data': {
'delta': [
{'insert': 'Join our '},
{
'insert': 'Discord Server',
'attributes': {'href': 'https://discord.com/invite/KmG4wQnnD9'}
},
{'insert': ' and chat with the team.'}
]
}
},
{
'type': 'bulleted_list',
'data': {
'delta': [
{'insert': 'Playground code can be found '},
{
'insert': 'here',
'attributes': {
'href':
'https://github.com/facebook/lexical/tree/main/packages/lexical-playground'
}
},
{'insert': '.'}
]
}
},
{
'type': 'paragraph',
'data': {
'delta': [
{
'insert':
'Lastly, we\'re constantly adding cool new features to this playground. So make sure you check back here when you next get a chance 🙂.'
}
]
}
},
{
'type': 'paragraph',
'data': {'delta': []}
}
]
}
};
Loading

0 comments on commit 3847701

Please sign in to comment.