Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: nested elements getting ignored #178

Merged
merged 3 commits into from
Jun 11, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
alihassan143 marked this conversation as resolved.
Show resolved Hide resolved
}) {
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 [
alihassan143 marked this conversation as resolved.
Show resolved Hide resolved
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