diff --git a/lib/src/plugins/html/encoder/parser/html_parser.dart b/lib/src/plugins/html/encoder/parser/html_parser.dart index e394f24dc..d995c894d 100644 --- a/lib/src/plugins/html/encoder/parser/html_parser.dart +++ b/lib/src/plugins/html/encoder/parser/html_parser.dart @@ -4,5 +4,6 @@ export 'html_node_parser.dart'; export 'image_node_parser.dart'; export 'numbered_list_node_parser.dart'; export 'quote_node_parser.dart'; +export 'table_node_parser.dart'; export 'text_node_parser.dart'; export 'todo_list_node_parser.dart'; diff --git a/lib/src/plugins/html/encoder/parser/table_node_parser.dart b/lib/src/plugins/html/encoder/parser/table_node_parser.dart new file mode 100644 index 000000000..682cf1ef7 --- /dev/null +++ b/lib/src/plugins/html/encoder/parser/table_node_parser.dart @@ -0,0 +1,69 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:collection/collection.dart'; +import 'package:html/dom.dart' as dom; + +import '../../../../editor/block_component/table_block_component/util.dart'; + +class HtmlTableNodeParser extends HTMLNodeParser { + const HtmlTableNodeParser(); + + @override + String get id => TableBlockKeys.type; + + @override + String transformNodeToHTMLString( + Node node, { + required List encodeParsers, + }) { + assert(node.type == TableBlockKeys.type); + + return toHTMLString( + transformNodeToDomNodes(node, encodeParsers: encodeParsers), + ); + } + + @override + List transformNodeToDomNodes( + Node node, { + required List encodeParsers, + }) { + final int rowsLen = node.attributes[TableBlockKeys.rowsLen], + colsLen = node.attributes[TableBlockKeys.colsLen]; + final List domNodes = []; + + for (var i = 0; i < rowsLen; i++) { + final List nodes = []; + for (var j = 0; j < colsLen; j++) { + final Node cell = getCellNode(node, j, i)!; + + for (final childnode in cell.children) { + HTMLNodeParser? parser = encodeParsers.firstWhereOrNull( + (element) => element.id == childnode.type, + ); + + if (parser != null) { + nodes.add( + wrapChildrenNodesWithTagName( + HTMLTags.tabledata, + childNodes: parser.transformNodeToDomNodes( + childnode, + encodeParsers: encodeParsers, + ), + ), + ); + } + } + } + final rowelement = + wrapChildrenNodesWithTagName(HTMLTags.tableRow, childNodes: nodes); + + domNodes.add(rowelement); + } + + final element = + wrapChildrenNodesWithTagName(HTMLTags.table, childNodes: domNodes); + return [ + element, + ]; + } +} diff --git a/lib/src/plugins/html/html_document.dart b/lib/src/plugins/html/html_document.dart index bb57c3280..dc359cb4d 100644 --- a/lib/src/plugins/html/html_document.dart +++ b/lib/src/plugins/html/html_document.dart @@ -28,6 +28,7 @@ String documentToHTML( const HTMLQuoteNodeParser(), const HTMLHeadingNodeParser(), const HTMLImageNodeParser(), + const HtmlTableNodeParser() ], ).encode(document); } diff --git a/lib/src/plugins/html/html_document_decoder.dart b/lib/src/plugins/html/html_document_decoder.dart index a0e200523..cb8ed87d3 100644 --- a/lib/src/plugins/html/html_document_decoder.dart +++ b/lib/src/plugins/html/html_document_decoder.dart @@ -2,6 +2,7 @@ import 'dart:collection'; import 'dart:convert'; import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor/src/editor/block_component/table_block_component/table_node.dart'; import 'package:html/dom.dart' as dom; import 'package:html/parser.dart' show parse; @@ -78,12 +79,14 @@ class DocumentHTMLDecoder extends Converter { return _parseUnOrderListElement(element); case HTMLTags.orderedList: return _parseOrderListElement(element); + case HTMLTags.table: + return _parseTable(element); case HTMLTags.list: return [ _parseListElement( element, type: type, - ) + ), ]; case HTMLTags.paragraph: return _parseParagraphElement(element); @@ -96,6 +99,134 @@ class DocumentHTMLDecoder extends Converter { } } + Iterable _parseTable(dom.Element element) { + final List tablenodes = []; + int columnLenth = 0; + int rowLength = 0; + for (final data in element.children) { + final (col, row, rwdata) = _parsetableRows(data); + columnLenth = columnLenth + col; + rowLength = rowLength + row; + + tablenodes.addAll(rwdata); + } + + return [ + TableNode( + node: Node( + type: TableBlockKeys.type, + attributes: { + TableBlockKeys.rowsLen: rowLength, + TableBlockKeys.colsLen: columnLenth, + TableBlockKeys.colDefaultWidth: TableDefaults.colWidth, + TableBlockKeys.rowDefaultHeight: TableDefaults.rowHeight, + TableBlockKeys.colMinimumWidth: TableDefaults.colMinimumWidth, + }, + children: tablenodes, + ), + ).node, + ]; + } + + (int, int, List) _parsetableRows(dom.Element element) { + final List nodes = []; + int colLength = 0; + int rowLength = 0; + + for (final data in element.children) { + final tabledata = _parsetableData(data, rowPosition: rowLength); + if (colLength == 0) { + colLength = tabledata.length; + } + nodes.addAll(tabledata); + rowLength++; + } + return (colLength, rowLength, nodes); + } + + Iterable _parsetableData( + dom.Element element, { + required int rowPosition, + }) { + final List nodes = []; + int columnPosition = 0; + + for (final data in element.children) { + Attributes attributes = { + TableCellBlockKeys.colPosition: columnPosition, + TableCellBlockKeys.rowPosition: rowPosition, + }; + if (data.attributes.isNotEmpty) { + final deltaAttributes = _getDeltaAttributesFromHTMLAttributes( + element.attributes, + ) ?? + {}; + attributes.addAll(deltaAttributes); + } + + List children; + if (data.children.isEmpty) { + children = [paragraphNode(text: data.text)]; + } else { + children = _parseTableSpecialNodes(data).toList(); + } + + final node = Node( + type: TableCellBlockKeys.type, + attributes: attributes, + children: children, + ); + + nodes.add(node); + columnPosition++; + } + + return nodes; + } + + Iterable _parseTableSpecialNodes(dom.Element element) { + final List nodes = []; + + if (element.children.isNotEmpty) { + for (final childrens in element.children) { + nodes.addAll(_parseTableDataElementsData(childrens)); + } + } else { + nodes.addAll(_parseTableDataElementsData(element)); + } + return nodes; + } + + List _parseTableDataElementsData(dom.Element element) { + final List nodes = []; + final delta = Delta(); + final localName = element.localName; + + if (HTMLTags.formattingElements.contains(localName)) { + final attributes = _parserFormattingElementAttributes(element); + delta.insert(element.text, attributes: attributes); + } else if (HTMLTags.specialElements.contains(localName)) { + if (delta.isNotEmpty) { + nodes.add(paragraphNode(delta: delta)); + } + nodes.addAll( + _parseSpecialElements( + element, + type: ParagraphBlockKeys.type, + ), + ); + } else if (element is dom.Text) { + // skip the empty text node + + delta.insert(element.text); + } + + if (delta.isNotEmpty) { + nodes.add(paragraphNode(delta: delta)); + } + return nodes; + } + Attributes _parserFormattingElementAttributes( dom.Element element, ) { @@ -130,6 +261,7 @@ class DocumentHTMLDecoder extends Converter { attributes = {AppFlowyRichTextKeys.href: href}; } break; + case HTMLTags.strikethrough: attributes = {AppFlowyRichTextKeys.strikethrough: true}; break; @@ -152,7 +284,7 @@ class DocumentHTMLDecoder extends Converter { level: level, delta: delta, ), - ...specialNodes + ...specialNodes, ]; } @@ -360,6 +492,10 @@ class HTMLTags { static const blockQuote = 'blockquote'; static const div = 'div'; static const divider = 'hr'; + static const table = 'table'; + static const tableRow = 'tr'; + static const tableheader = "th"; + static const tabledata = "td"; static const section = 'section'; static const font = 'font'; @@ -385,6 +521,7 @@ class HTMLTags { HTMLTags.orderedList, HTMLTags.div, HTMLTags.list, + HTMLTags.table, HTMLTags.paragraph, HTMLTags.blockQuote, HTMLTags.checkbox, @@ -396,6 +533,7 @@ class HTMLTags { return tag == h1 || tag == h2 || tag == h3 || + tag == table || tag == checkbox || tag == paragraph || tag == div || diff --git a/test/plugins/html/decoder/document_html_decoder_test.dart b/test/plugins/html/decoder/document_html_decoder_test.dart index cd41bf3d8..58c48c717 100644 --- a/test/plugins/html/decoder/document_html_decoder_test.dart +++ b/test/plugins/html/decoder/document_html_decoder_test.dart @@ -12,6 +12,11 @@ void main() async { expect(result.toJson(), example); }); + test('html table parser document', () async { + final result = DocumentHTMLDecoder().convert(htmlTable); + + expect(result.toJson(), htmlTablejson); + }); test('nested parser document', () async { final result = DocumentHTMLDecoder().convert(nestedHTML); expect(result.toJson(), nestedDelta); @@ -21,6 +26,126 @@ void main() async { const rawHTML = '''

AppFlowyEditor

👋 Welcome to AppFlowy Editor

AppFlowy Editor is a highly customizable rich-text editor

Here is an example your you can give a try

Span element

Span element two

Span element three

This is an anchor tag!

Features!

  • [x] Customizable
  • [x] Test-covered
  • [ ] more to come!
  • First item
  • Second item
  • List element
This is a quote!

Code block

Italic one

Italic two

Bold tag

You can also use AppFlowy Editor as a component to build your own app.

Awesome features

If you have questions or feedback, please submit an issue on Github or join the community along with 1000+ builders!

'''; +const htmlTable = + ''''

a

c

b

d

'''; +const htmlTablejson = { + "document": { + "type": "page", + "children": [ + { + "type": "paragraph", + "data": { + "delta": [ + {"insert": "'"}, + ], + }, + }, + { + "type": "table", + "children": [ + { + "type": "table/cell", + "children": [ + { + "type": "heading", + "data": { + "delta": [ + {"insert": "a"}, + ], + "level": 2, + }, + } + ], + "data": { + "colPosition": 0, + "rowPosition": 0, + "height": 40.0, + "width": 80.0, + }, + }, + { + "type": "table/cell", + "children": [ + { + "type": "paragraph", + "data": { + "delta": [ + { + "insert": "c", + "attributes": {"italic": true}, + } + ], + }, + } + ], + "data": { + "colPosition": 1, + "rowPosition": 0, + "height": 40.0, + "width": 80.0, + }, + }, + { + "type": "table/cell", + "children": [ + { + "type": "paragraph", + "data": { + "delta": [ + { + "insert": "b", + "attributes": {"bold": true}, + } + ], + }, + } + ], + "data": { + "colPosition": 0, + "rowPosition": 1, + "height": 40.0, + "width": 80.0, + }, + }, + { + "type": "table/cell", + "children": [ + { + "type": "paragraph", + "data": { + "delta": [ + {"insert": "d"}, + ], + }, + } + ], + "data": { + "colPosition": 1, + "rowPosition": 1, + "height": 40.0, + "width": 80.0, + }, + } + ], + "data": { + "rowsLen": 2, + "colsLen": 2, + "colDefaultWidth": 80, + "rowDefaultHeight": 40, + "colMinimumWidth": 40, + }, + }, + { + "type": "paragraph", + "data": { + "delta": [ + {"insert": "'"}, + ], + }, + } + ], + }, +}; const example = { 'document': { @@ -31,9 +156,9 @@ const example = { 'data': { 'level': 1, 'delta': [ - {'insert': 'AppFlowyEditor'} - ] - } + {'insert': 'AppFlowyEditor'}, + ], + }, }, { 'type': 'heading', @@ -43,15 +168,15 @@ const example = { {'insert': '👋 '}, { 'insert': 'Welcome to', - 'attributes': {'bold': true} + 'attributes': {'bold': true}, }, {'insert': ' '}, { 'insert': 'AppFlowy Editor', - 'attributes': {'bold': true, 'italic': true} + 'attributes': {'bold': true, 'italic': true}, } - ] - } + ], + }, }, { 'type': 'paragraph', @@ -60,15 +185,15 @@ const example = { {'insert': 'AppFlowy Editor is a '}, { 'insert': 'highly customizable', - 'attributes': {'bold': true} + 'attributes': {'bold': true}, }, {'insert': ' '}, { 'insert': 'rich-text editor', - 'attributes': {'italic': true} + 'attributes': {'italic': true}, } - ] - } + ], + }, }, { 'type': 'paragraph', @@ -77,16 +202,16 @@ const example = { {'insert': ' '}, { 'insert': 'Here', - 'attributes': {'underline': true} + 'attributes': {'underline': true}, }, {'insert': ' is an example '}, { 'insert': 'your', - 'attributes': {'strikethrough': true} + 'attributes': {'strikethrough': true}, }, - {'insert': ' you can give a try'} - ] - } + {'insert': ' you can give a try'}, + ], + }, }, { 'type': 'paragraph', @@ -95,10 +220,10 @@ const example = { {'insert': ' '}, { 'insert': 'Span element', - 'attributes': {'bold': true, 'italic': true} + 'attributes': {'bold': true, 'italic': true}, } - ] - } + ], + }, }, { 'type': 'paragraph', @@ -107,10 +232,10 @@ const example = { {'insert': ' '}, { 'insert': 'Span element two', - 'attributes': {'underline': true} + 'attributes': {'underline': true}, } - ] - } + ], + }, }, { 'type': 'paragraph', @@ -119,10 +244,10 @@ const example = { {'insert': ' '}, { 'insert': 'Span element three', - 'attributes': {'bold': true, 'strikethrough': true} + 'attributes': {'bold': true, 'strikethrough': true}, } - ] - } + ], + }, }, { 'type': 'paragraph', @@ -131,75 +256,75 @@ const example = { {'insert': ' '}, { 'insert': 'This is an anchor tag!', - 'attributes': {'href': 'https://appflowy.io'} + 'attributes': {'href': 'https://appflowy.io'}, } - ] - } + ], + }, }, { 'type': 'heading', 'data': { 'level': 3, 'delta': [ - {'insert': 'Features!'} - ] - } + {'insert': 'Features!'}, + ], + }, }, { 'type': 'bulleted_list', 'data': { 'delta': [ - {'insert': '[x] Customizable'} - ] - } + {'insert': '[x] Customizable'}, + ], + }, }, { 'type': 'bulleted_list', 'data': { 'delta': [ - {'insert': '[x] Test-covered'} - ] - } + {'insert': '[x] Test-covered'}, + ], + }, }, { 'type': 'bulleted_list', 'data': { 'delta': [ - {'insert': '[ ] more to come!'} - ] - } + {'insert': '[ ] more to come!'}, + ], + }, }, { 'type': 'bulleted_list', 'data': { 'delta': [ - {'insert': 'First item'} - ] - } + {'insert': 'First item'}, + ], + }, }, { 'type': 'bulleted_list', 'data': { 'delta': [ - {'insert': 'Second item'} - ] - } + {'insert': 'Second item'}, + ], + }, }, { 'type': 'bulleted_list', 'data': { 'delta': [ - {'insert': 'List element'} - ] - } + {'insert': 'List element'}, + ], + }, }, { 'type': 'quote', 'data': { 'delta': [ - {'insert': 'This is a quote!'} - ] - } + {'insert': 'This is a quote!'}, + ], + }, }, { 'type': 'paragraph', @@ -207,10 +332,10 @@ const example = { 'delta': [ { 'insert': ' Code block', - 'attributes': {'code': true} + 'attributes': {'code': true}, } - ] - } + ], + }, }, { 'type': 'paragraph', @@ -219,10 +344,10 @@ const example = { {'insert': ' '}, { 'insert': 'Italic one', - 'attributes': {'italic': true} + 'attributes': {'italic': true}, } - ] - } + ], + }, }, { 'type': 'paragraph', @@ -231,10 +356,10 @@ const example = { {'insert': ' '}, { 'insert': 'Italic two', - 'attributes': {'italic': true} + 'attributes': {'italic': true}, } - ] - } + ], + }, }, { 'type': 'paragraph', @@ -243,10 +368,10 @@ const example = { {'insert': ' '}, { 'insert': 'Bold tag', - 'attributes': {'bold': true} + 'attributes': {'bold': true}, } - ] - } + ], + }, }, { 'type': 'paragraph', @@ -255,20 +380,20 @@ const example = { {'insert': 'You can also use '}, { 'insert': 'AppFlowy Editor', - 'attributes': {'bold': true, 'italic': true} + 'attributes': {'bold': true, 'italic': true}, }, - {'insert': ' as a component to build your own app. '} - ] - } + {'insert': ' as a component to build your own app. '}, + ], + }, }, { 'type': 'heading', 'data': { 'level': 3, 'delta': [ - {'insert': 'Awesome features'} - ] - } + {'insert': 'Awesome features'}, + ], + }, }, { 'type': 'paragraph', @@ -276,21 +401,21 @@ const example = { 'delta': [ { 'insert': - 'If you have questions or feedback, please submit an issue on Github or join the community along with 1000+ builders!' + 'If you have questions or feedback, please submit an issue on Github or join the community along with 1000+ builders!', } - ] - } + ], + }, }, { 'type': 'paragraph', - 'data': {'delta': []} + 'data': {'delta': []}, }, { 'type': 'paragraph', - 'data': {'delta': []} + 'data': {'delta': []}, } - ] - } + ], + }, }; const nestedHTML = '''

Welcome to the playground

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 @lexical/react. Try typing in some text with different formats.

Make sure to check out the various plugins in the toolbar. You can also use #hashtags or @-mentions too!

If you'd like to find out more about Lexical, you can:

  • Playground code can be found here.
  • Playground code can be found here.

Lastly, we're constantly adding cool new features to this playground. So make sure you check back here when you next get a chance 🙂.

'''; @@ -303,9 +428,9 @@ const nestedDelta = { 'data': { 'level': 1, 'delta': [ - {'insert': 'Welcome to the playground'} - ] - } + {'insert': 'Welcome to the playground'}, + ], + }, }, { 'type': 'quote', @@ -313,11 +438,11 @@ const nestedDelta = { '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 ' + '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} + 'attributes': {'code': true}, }, {'insert': '. Try typing in '}, { @@ -325,24 +450,24 @@ const nestedDelta = { 'attributes': { 'bold': true, "italic": true, - 'href': 'https://appflowy.io' - } + 'href': 'https://appflowy.io', + }, }, {'insert': ' with '}, { 'insert': 'different', - 'attributes': {'italic': true} + 'attributes': {'italic': true}, }, - {'insert': ' formats.'} - ] - } + {'insert': ' formats.'}, + ], + }, }, { 'type': 'image', 'data': { 'url': 'https://richtexteditor.com/images/editor-image.png', 'align': 'center', - } + }, }, { 'type': 'paragraph', @@ -350,14 +475,14 @@ const nestedDelta = { 'delta': [ { 'insert': - 'Make sure to check out the various plugins in the toolbar. You can also use #hashtags or @-mentions too!' + 'Make sure to check out the various plugins in the toolbar. You can also use #hashtags or @-mentions too!', } - ] - } + ], + }, }, { 'type': 'paragraph', - 'data': {'delta': []} + 'data': {'delta': []}, }, { 'type': 'paragraph', @@ -365,10 +490,10 @@ const nestedDelta = { 'delta': [ { 'insert': - 'If you\'d like to find out more about Lexical, you can:' + 'If you\'d like to find out more about Lexical, you can:', } - ] - } + ], + }, }, { 'type': 'bulleted_list', @@ -377,11 +502,11 @@ const nestedDelta = { {'insert': 'Visit the '}, { 'insert': 'Lexical website', - 'attributes': {'href': 'https://lexical.dev/'} + 'attributes': {'href': 'https://lexical.dev/'}, }, - {'insert': ' for documentation and more information.'} - ] - } + {'insert': ' for documentation and more information.'}, + ], + }, }, { 'type': 'bulleted_list', @@ -391,10 +516,10 @@ const nestedDelta = { 'data': { 'url': 'https://richtexteditor.com/images/editor-image.png', 'align': 'center', - } + }, } ], - 'data': {'delta': []} + 'data': {'delta': []}, }, { 'type': 'bulleted_list', @@ -403,11 +528,11 @@ const nestedDelta = { {'insert': 'Check out the code on our '}, { 'insert': 'GitHub repository', - 'attributes': {'href': 'https://github.com/facebook/lexical'} + 'attributes': {'href': 'https://github.com/facebook/lexical'}, }, - {'insert': '.'} - ] - } + {'insert': '.'}, + ], + }, }, { 'type': 'bulleted_list', @@ -418,12 +543,12 @@ const nestedDelta = { 'insert': 'here', 'attributes': { 'href': - 'https://github.com/facebook/lexical/tree/main/packages/lexical-playground' - } + 'https://github.com/facebook/lexical/tree/main/packages/lexical-playground', + }, }, - {'insert': '.'} - ] - } + {'insert': '.'}, + ], + }, }, { 'type': 'bulleted_list', @@ -432,11 +557,11 @@ const nestedDelta = { {'insert': 'Join our '}, { 'insert': 'Discord Server', - 'attributes': {'href': 'https://discord.com/invite/KmG4wQnnD9'} + 'attributes': {'href': 'https://discord.com/invite/KmG4wQnnD9'}, }, - {'insert': ' and chat with the team.'} - ] - } + {'insert': ' and chat with the team.'}, + ], + }, }, { 'type': 'bulleted_list', @@ -447,12 +572,12 @@ const nestedDelta = { 'insert': 'here', 'attributes': { 'href': - 'https://github.com/facebook/lexical/tree/main/packages/lexical-playground' - } + 'https://github.com/facebook/lexical/tree/main/packages/lexical-playground', + }, }, - {'insert': '.'} - ] - } + {'insert': '.'}, + ], + }, }, { 'type': 'paragraph', @@ -460,15 +585,15 @@ const nestedDelta = { '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 🙂.' + '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': []} + 'data': {'delta': []}, } - ] - } + ], + }, }; diff --git a/test/plugins/html/encoder/document_html_encoder_test.dart b/test/plugins/html/encoder/document_html_encoder_test.dart index 2a6402b2c..92f5be689 100644 --- a/test/plugins/html/encoder/document_html_encoder_test.dart +++ b/test/plugins/html/encoder/document_html_encoder_test.dart @@ -10,6 +10,7 @@ void main() async { const HTMLQuoteNodeParser(), const HTMLHeadingNodeParser(), const HTMLImageNodeParser(), + const HtmlTableNodeParser() ]; group('document_html_encoder_test.dart', () { setUpAll(() { diff --git a/test/plugins/html/encoder/parser/image_node_parser_test.dart b/test/plugins/html/encoder/parser/image_node_parser_test.dart index 6f2d9e447..5ff0e036e 100644 --- a/test/plugins/html/encoder/parser/image_node_parser_test.dart +++ b/test/plugins/html/encoder/parser/image_node_parser_test.dart @@ -10,6 +10,7 @@ void main() async { const HTMLQuoteNodeParser(), const HTMLHeadingNodeParser(), const HTMLImageNodeParser(), + const HtmlTableNodeParser() ]; group('html_image_node_parser.dart', () { test('parser image node', () { diff --git a/test/plugins/html/encoder/parser/table_node_parser_test.dart b/test/plugins/html/encoder/parser/table_node_parser_test.dart new file mode 100644 index 000000000..ba7550693 --- /dev/null +++ b/test/plugins/html/encoder/parser/table_node_parser_test.dart @@ -0,0 +1,116 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor/src/editor/block_component/table_block_component/table_node.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() async { + List parser = [ + const HTMLTextNodeParser(), + const HTMLBulletedListNodeParser(), + const HTMLNumberedListNodeParser(), + const HTMLTodoListNodeParser(), + const HTMLQuoteNodeParser(), + const HTMLHeadingNodeParser(), + const HTMLImageNodeParser(), + const HtmlTableNodeParser() + ]; + group('html_image_node_parser.dart', () { + test('table node parser test', () { + final tableNode = TableNode.fromJson({ + 'type': TableBlockKeys.type, + 'data': { + TableBlockKeys.colsLen: 2, + TableBlockKeys.rowsLen: 2, + TableBlockKeys.colDefaultWidth: 60, + TableBlockKeys.rowDefaultHeight: 50, + TableBlockKeys.colMinimumWidth: 30, + }, + 'children': [ + { + 'type': TableCellBlockKeys.type, + 'data': { + TableCellBlockKeys.colPosition: 0, + TableCellBlockKeys.rowPosition: 0, + TableCellBlockKeys.width: 35, + }, + 'children': [ + { + 'type': 'heading', + 'data': { + 'level': 2, + 'delta': [ + {'insert': 'a'}, + ], + }, + } + ], + }, + { + 'type': TableCellBlockKeys.type, + 'data': { + TableCellBlockKeys.colPosition: 0, + TableCellBlockKeys.rowPosition: 1, + }, + 'children': [ + { + 'type': 'paragraph', + 'data': { + 'delta': [ + { + 'insert': 'b', + 'attributes': {'bold': true}, + } + ], + }, + } + ], + }, + { + 'type': TableCellBlockKeys.type, + 'data': { + TableCellBlockKeys.colPosition: 1, + TableCellBlockKeys.rowPosition: 0, + }, + 'children': [ + { + 'type': 'paragraph', + 'data': { + 'delta': [ + { + 'insert': 'c', + 'attributes': {'italic': true}, + } + ], + }, + } + ], + }, + { + 'type': TableCellBlockKeys.type, + 'data': { + TableCellBlockKeys.colPosition: 1, + TableCellBlockKeys.rowPosition: 1, + }, + 'children': [ + { + 'type': 'paragraph', + 'data': { + 'delta': [ + {'insert': 'd'}, + ], + }, + } + ], + } + ], + }); + + expect( + const HtmlTableNodeParser().transformNodeToHTMLString( + tableNode.node, + encodeParsers: parser, + ), + '''

a

c

b

d

''', + ); + }); + }); +} diff --git a/test/plugins/html/encoder/parser/text_node_parser_test.dart b/test/plugins/html/encoder/parser/text_node_parser_test.dart index 0166cdbce..5a9f2be7b 100644 --- a/test/plugins/html/encoder/parser/text_node_parser_test.dart +++ b/test/plugins/html/encoder/parser/text_node_parser_test.dart @@ -10,6 +10,7 @@ void main() async { const HTMLQuoteNodeParser(), const HTMLHeadingNodeParser(), const HTMLImageNodeParser(), + const HtmlTableNodeParser(), ]; group('html_text_node_parser.dart', () { const text = 'Welcome to AppFlowy'; @@ -240,7 +241,6 @@ void main() async { .toJson(), }, ); - expect( const HTMLTextNodeParser() .transformNodeToHTMLString(node, encodeParsers: parser),