From fbf6389ee4b6f36238f0626b55fb4def9594a51e Mon Sep 17 00:00:00 2001 From: CatHood0 Date: Thu, 27 Jun 2024 22:21:21 -0400 Subject: [PATCH 1/9] fixed #1953 italic detection error fix: issue where when html2md parse to "_" instead of "*" that won't be detected MarkdownToDelta converter feat(test): added test for DeltaX feat: added config classes to MarkdownToDelta and html2md that allow users configure their own styles --- lib/src/models/documents/delta_x.dart | 85 +++++++++++++++++++++++++-- test/utils/delta_x_test.dart | 22 +++++++ 2 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 test/utils/delta_x_test.dart diff --git a/lib/src/models/documents/delta_x.dart b/lib/src/models/documents/delta_x.dart index 0762fa151..754c1e06a 100644 --- a/lib/src/models/documents/delta_x.dart +++ b/lib/src/models/documents/delta_x.dart @@ -1,7 +1,6 @@ import 'package:html2md/html2md.dart' as html2md; import 'package:markdown/markdown.dart' as md; import 'package:meta/meta.dart'; - import '../../../markdown_quill.dart'; import '../../../quill_delta.dart'; @@ -15,9 +14,20 @@ class DeltaX { /// This api is **experimental** and designed to be used **internally** and shouldn't /// used for **production applications**. @experimental - static Delta fromMarkdown(String markdownText) { + static Delta fromMarkdown( + String markdownText, { + Md2DeltaConfigs md2DeltaConfigs = const Md2DeltaConfigs(), + }) { final mdDocument = md.Document(encodeHtml: false); - final mdToDelta = MarkdownToDelta(markdownDocument: mdDocument); + final mdToDelta = MarkdownToDelta( + markdownDocument: mdDocument, + customElementToBlockAttribute: + md2DeltaConfigs.customElementToBlockAttribute, + customElementToEmbeddable: md2DeltaConfigs.customElementToEmbeddable, + customElementToInlineAttribute: + md2DeltaConfigs.customElementToInlineAttribute, + softLineBreak: md2DeltaConfigs.softLineBreak, + ); return mdToDelta.convert(markdownText); } @@ -32,9 +42,72 @@ class DeltaX { /// used for **production applications**. /// @experimental - static Delta fromHtml(String htmlText) { - final markdownText = html2md.convert(htmlText).replaceAll('unsafe:', ''); - + static Delta fromHtml(String htmlText, {Html2MdConfigs? configs}) { + final markdownText = html2md + .convert( + htmlText, + rules: configs?.customRules, + ignore: configs?.ignoreIf, + rootTag: configs?.rootTag, + imageBaseUrl: configs?.imageBaseUrl, + styleOptions: configs?.styleOptions, + ) + .replaceAll( + 'unsafe:', + '', + ); return fromMarkdown(markdownText); } } + +@immutable +@experimental +class Md2DeltaConfigs { + const Md2DeltaConfigs({ + this.customElementToInlineAttribute = const {}, + this.customElementToBlockAttribute = const {}, + this.customElementToEmbeddable = const {}, + this.softLineBreak = false, + }); + final Map customElementToInlineAttribute; + final Map customElementToBlockAttribute; + final Map customElementToEmbeddable; + final bool softLineBreak; +} + +@immutable +@experimental +class Html2MdConfigs { + const Html2MdConfigs({ + this.customRules, + this.ignoreIf, + this.rootTag, + this.imageBaseUrl, + this.styleOptions = const {'emDelimiter': '*'}, + //emDelimiter set em to be "*" instead a "_" + }); + + /// The [rules] parameter can be used to customize element processing. + final List? customRules; + + /// Elements list in [ignore] would be ingored. + final List? ignoreIf; + + final String? rootTag; + final String? imageBaseUrl; + + /// The default and available style options: + /// + /// | Name | Default | Options | + /// | ------------- |:-------------:| -----:| + /// | headingStyle | "setext" | "setext", "atx" | + /// | hr | "* * *" | "* * *", "- - -", "_ _ _" | + /// | bulletListMarker | "*" | "*", "-", "_" | + /// | codeBlockStyle | "indented" | "indented", "fenced" | + /// | fence | "\`\`\`" | "\`\`\`", "~~~" | + /// | emDelimiter | "_" | "_", "*" | + /// | strongDelimiter | "**" | "**", "__" | + /// | linkStyle | "inlined" | "inlined", "referenced" | + /// | linkReferenceStyle | "full" | "full", "collapsed", "shortcut" | + final Map? styleOptions; +} diff --git a/test/utils/delta_x_test.dart b/test/utils/delta_x_test.dart new file mode 100644 index 000000000..a6b8fcee4 --- /dev/null +++ b/test/utils/delta_x_test.dart @@ -0,0 +1,22 @@ +import 'package:flutter_quill/quill_delta.dart'; +import 'package:flutter_quill/src/models/documents/delta_x.dart'; +import 'package:test/test.dart'; + +void main() { + const htmlWithEmp = + '

This is a normal sentence, and this section has greater emphasis.

'; + final expectedDeltaEmp = Delta.fromOperations([ + Operation.insert( + 'This is a normal sentence, and this section has greater emp'), + Operation.insert('hasis.', {'italic': true}), + Operation.insert('\n'), + ]); + + test('should detect emphasis and parse correctly', () { + final delta = DeltaX.fromHtml( + htmlWithEmp, + configs: const Html2MdConfigs(), + ); + expect(delta, expectedDeltaEmp); + }); +} From bbdc68cd26919bedc39c218592f13d0c97b844d2 Mon Sep 17 00:00:00 2001 From: CatHood0 Date: Fri, 28 Jun 2024 14:46:15 -0400 Subject: [PATCH 2/9] Added support for html underline and videos --- lib/src/models/documents/delta_x.dart | 69 ++++-------- .../quill_markdown/markdown_to_delta.dart | 10 ++ lib/src/utils/html2md_utils.dart | 105 ++++++++++++++++++ test/utils/delta_x_test.dart | 28 ++++- 4 files changed, 163 insertions(+), 49 deletions(-) create mode 100644 lib/src/utils/html2md_utils.dart diff --git a/lib/src/models/documents/delta_x.dart b/lib/src/models/documents/delta_x.dart index 754c1e06a..eb6777f0c 100644 --- a/lib/src/models/documents/delta_x.dart +++ b/lib/src/models/documents/delta_x.dart @@ -1,8 +1,10 @@ +import 'package:flutter/foundation.dart'; import 'package:html2md/html2md.dart' as html2md; import 'package:markdown/markdown.dart' as md; import 'package:meta/meta.dart'; import '../../../markdown_quill.dart'; import '../../../quill_delta.dart'; +import '../../utils/html2md_utils.dart'; @immutable @experimental @@ -18,7 +20,8 @@ class DeltaX { String markdownText, { Md2DeltaConfigs md2DeltaConfigs = const Md2DeltaConfigs(), }) { - final mdDocument = md.Document(encodeHtml: false); + final mdDocument = md.Document( + encodeHtml: false, inlineSyntaxes: [UnderlineSyntax(), VideoSyntax()]); final mdToDelta = MarkdownToDelta( markdownDocument: mdDocument, customElementToBlockAttribute: @@ -42,15 +45,29 @@ class DeltaX { /// used for **production applications**. /// @experimental - static Delta fromHtml(String htmlText, {Html2MdConfigs? configs}) { + static Delta fromHtml( + String htmlText, { + Html2MdConfigs? configs, + }) { + configs = Html2MdConfigs( + customRules: [ + Html2MdRules.underlineRule, + Html2MdRules.videoRule, + ...configs?.customRules ?? [] + ], + ignoreIf: configs?.ignoreIf, + rootTag: configs?.rootTag, + imageBaseUrl: configs?.imageBaseUrl, + styleOptions: configs?.styleOptions ?? {'emDelimiter': '*'}, + ); final markdownText = html2md .convert( htmlText, - rules: configs?.customRules, - ignore: configs?.ignoreIf, - rootTag: configs?.rootTag, - imageBaseUrl: configs?.imageBaseUrl, - styleOptions: configs?.styleOptions, + rules: configs.customRules, + ignore: configs.ignoreIf, + rootTag: configs.rootTag, + imageBaseUrl: configs.imageBaseUrl, + styleOptions: configs.styleOptions, ) .replaceAll( 'unsafe:', @@ -60,7 +77,6 @@ class DeltaX { } } -@immutable @experimental class Md2DeltaConfigs { const Md2DeltaConfigs({ @@ -74,40 +90,3 @@ class Md2DeltaConfigs { final Map customElementToEmbeddable; final bool softLineBreak; } - -@immutable -@experimental -class Html2MdConfigs { - const Html2MdConfigs({ - this.customRules, - this.ignoreIf, - this.rootTag, - this.imageBaseUrl, - this.styleOptions = const {'emDelimiter': '*'}, - //emDelimiter set em to be "*" instead a "_" - }); - - /// The [rules] parameter can be used to customize element processing. - final List? customRules; - - /// Elements list in [ignore] would be ingored. - final List? ignoreIf; - - final String? rootTag; - final String? imageBaseUrl; - - /// The default and available style options: - /// - /// | Name | Default | Options | - /// | ------------- |:-------------:| -----:| - /// | headingStyle | "setext" | "setext", "atx" | - /// | hr | "* * *" | "* * *", "- - -", "_ _ _" | - /// | bulletListMarker | "*" | "*", "-", "_" | - /// | codeBlockStyle | "indented" | "indented", "fenced" | - /// | fence | "\`\`\`" | "\`\`\`", "~~~" | - /// | emDelimiter | "_" | "_", "*" | - /// | strongDelimiter | "**" | "**", "__" | - /// | linkStyle | "inlined" | "inlined", "referenced" | - /// | linkReferenceStyle | "full" | "full", "collapsed", "shortcut" | - final Map? styleOptions; -} diff --git a/lib/src/packages/quill_markdown/markdown_to_delta.dart b/lib/src/packages/quill_markdown/markdown_to_delta.dart index 53194f774..2fd0dc39e 100644 --- a/lib/src/packages/quill_markdown/markdown_to_delta.dart +++ b/lib/src/packages/quill_markdown/markdown_to_delta.dart @@ -1,5 +1,6 @@ import 'dart:collection'; import 'dart:convert'; +import 'dart:math'; import 'package:collection/collection.dart'; import 'package:markdown/markdown.dart' as md; @@ -82,6 +83,8 @@ class MarkdownToDelta extends Converter final _elementToInlineAttr = { 'em': (_) => [Attribute.italic], + 'u': (_) => [Attribute.underline], + 'ins': (_) => [Attribute.underline], 'strong': (_) => [Attribute.bold], 'del': (_) => [Attribute.strikeThrough], 'a': (element) => [LinkAttribute(element.attributes['href'])], @@ -91,6 +94,7 @@ class MarkdownToDelta extends Converter final _elementToEmbed = { 'hr': (_) => horizontalRule, 'img': (elAttrs) => BlockEmbed.image(elAttrs['src'] ?? ''), + 'video': (elAttrs) => BlockEmbed.video(elAttrs['src'] ?? '') }; var _delta = Delta(); @@ -121,6 +125,7 @@ class MarkdownToDelta extends Converter _topLevelNodes.addAll(mdNodes); for (final node in mdNodes) { + print(node.toString()); node.accept(this); } @@ -173,6 +178,8 @@ class MarkdownToDelta extends Converter @override bool visitElementBefore(md.Element element) { + print( + 'Visit before: [tag: ${element.tag}, attributes: ${element.attributes}]'); _insertNewLineBeforeElementIfNeeded(element); final tag = element.tag; @@ -205,6 +212,9 @@ class MarkdownToDelta extends Converter void visitElementAfter(md.Element element) { final tag = element.tag; + print( + 'Visit after: [tag: ${element.tag}, attributes: ${element.attributes}]'); + if (_isEmbedElement(element)) { _delta.insert(_toEmbeddable(element).toJson()); } diff --git a/lib/src/utils/html2md_utils.dart b/lib/src/utils/html2md_utils.dart new file mode 100644 index 000000000..8f72de2f8 --- /dev/null +++ b/lib/src/utils/html2md_utils.dart @@ -0,0 +1,105 @@ +// ignore_for_file: implementation_imports + +import 'package:html2md/html2md.dart' as hmd; +import 'package:markdown/markdown.dart' as md; +import 'package:markdown/src/ast.dart' as ast; +import 'package:markdown/src/util.dart' as util; +import 'package:meta/meta.dart'; + +///Local syntax implementation for underline +class UnderlineSyntax extends md.DelimiterSyntax { + UnderlineSyntax() + : super( + '', + requiresDelimiterRun: true, + allowIntraWord: true, + tags: [md.DelimiterTag('u', 5)], + ); +} + +// [ character +const int $lbracket = 0x5B; +final RegExp youtubeVideoUrlValidator = RegExp( + r'^(?:https?:)?(?:\/\/)?(?:youtu\.be\/|(?:www\.|m\.)?youtube\.com\/(?:watch|v|embed)(?:\.php)?(?:\?.*v=|\/))([a-zA-Z0-9\_-]{7,15})(?:[\?&][a-zA-Z0-9\_-]+=[a-zA-Z0-9\_-]+)*(?:[&\/\#].*)?$'); + +class VideoSyntax extends md.LinkSyntax { + VideoSyntax({super.linkResolver}) + : super( + pattern: r'\[', + startCharacter: $lbracket, + ); + + @override + ast.Element createNode( + String destination, + String? title, { + required List Function() getChildren, + }) { + final element = md.Element.empty('video'); + element.attributes['src'] = util.normalizeLinkDestination( + util.escapePunctuation(destination), + ); + if (title != null && title.isNotEmpty) { + element.attributes['title'] = util.normalizeLinkTitle(title); + } + return element; + } +} + +class Html2MdRules { + const Html2MdRules._(); + + ///This rule avoid the default converter from html2md ignore underline tag for or + static final underlineRule = hmd.Rule('underline', filters: ['u', 'ins'], + replacement: (content, node) { + //Is used a local underline implemenation since markdown just use underline with html tags + return '$content'; + }); + static final videoRule = + hmd.Rule('video', filters: ['iframe'], replacement: (content, node) { + //by now, we can only access to src + final src = node.getAttribute('src'); + //if the source is null or is not valid youtube url, then just return the html instead remove it + //by now is only available validation for youtube videos + if (src == null || !youtubeVideoUrlValidator.hasMatch(src)) { + return node.outerHTML; + } + final title = node.getAttribute('title'); + return '[$title]($src)'; + }); +} + +@experimental +class Html2MdConfigs { + const Html2MdConfigs({ + this.customRules, + this.ignoreIf, + this.rootTag, + this.imageBaseUrl, + this.styleOptions, + }); + + /// The [rules] parameter can be used to customize element processing. + final List? customRules; + + /// Elements list in [ignore] would be ingored. + final List? ignoreIf; + + final String? rootTag; + final String? imageBaseUrl; + + /// The default and available style options: + /// + /// | Name | Default | Options | + /// | ------------- |:-------------:| -----:| + /// | headingStyle | "setext" | "setext", "atx" | + /// | hr | "* * *" | "* * *", "- - -", "_ _ _" | + /// | bulletListMarker | "*" | "*", "-", "_" | + /// | codeBlockStyle | "indented" | "indented", "fenced" | + /// | fence | "\`\`\`" | "\`\`\`", "~~~" | + /// | emDelimiter | "_" | "_", "*" | + /// | strongDelimiter | "**" | "**", "__" | + /// | linkStyle | "inlined" | "inlined", "referenced" | + /// | linkReferenceStyle | "full" | "full", "collapsed", "shortcut" | + final Map? styleOptions; +} diff --git a/test/utils/delta_x_test.dart b/test/utils/delta_x_test.dart index a6b8fcee4..4ce0dd4b4 100644 --- a/test/utils/delta_x_test.dart +++ b/test/utils/delta_x_test.dart @@ -5,6 +5,12 @@ import 'package:test/test.dart'; void main() { const htmlWithEmp = '

This is a normal sentence, and this section has greater emphasis.

'; + + const htmlWithUnderline = + '

This is a normal sentence, and this section has greater underline'; + + const htmlWithVideo = + ''; final expectedDeltaEmp = Delta.fromOperations([ Operation.insert( 'This is a normal sentence, and this section has greater emp'), @@ -12,11 +18,25 @@ void main() { Operation.insert('\n'), ]); + final expectedDeltaUnderline = Delta.fromOperations([ + Operation.insert( + 'This is a normal sentence, and this section has greater '), + Operation.insert('underline', {'underline': true}), + Operation.insert('\n'), + ]); + test('should detect emphasis and parse correctly', () { - final delta = DeltaX.fromHtml( - htmlWithEmp, - configs: const Html2MdConfigs(), - ); + final delta = DeltaX.fromHtml(htmlWithEmp); expect(delta, expectedDeltaEmp); }); + + test('should detect underline and parse correctly', () { + final delta = DeltaX.fromHtml(htmlWithUnderline); + expect(delta, expectedDeltaUnderline); + }); + + test('should detect video and parse correctly', () { + final delta = DeltaX.fromHtml(htmlWithVideo); + print(delta); + }); } From b66f8167b3b8607059632a1ad9c7fea052b3756b Mon Sep 17 00:00:00 2001 From: CatHood0 Date: Fri, 28 Jun 2024 14:51:33 -0400 Subject: [PATCH 3/9] removed print calls and fix no expect in test --- lib/src/packages/quill_markdown/markdown_to_delta.dart | 7 ------- lib/src/utils/html2md_utils.dart | 10 +++++----- test/utils/delta_x_test.dart | 7 ++++++- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/lib/src/packages/quill_markdown/markdown_to_delta.dart b/lib/src/packages/quill_markdown/markdown_to_delta.dart index 2fd0dc39e..c6870e965 100644 --- a/lib/src/packages/quill_markdown/markdown_to_delta.dart +++ b/lib/src/packages/quill_markdown/markdown_to_delta.dart @@ -1,6 +1,5 @@ import 'dart:collection'; import 'dart:convert'; -import 'dart:math'; import 'package:collection/collection.dart'; import 'package:markdown/markdown.dart' as md; @@ -125,7 +124,6 @@ class MarkdownToDelta extends Converter _topLevelNodes.addAll(mdNodes); for (final node in mdNodes) { - print(node.toString()); node.accept(this); } @@ -178,8 +176,6 @@ class MarkdownToDelta extends Converter @override bool visitElementBefore(md.Element element) { - print( - 'Visit before: [tag: ${element.tag}, attributes: ${element.attributes}]'); _insertNewLineBeforeElementIfNeeded(element); final tag = element.tag; @@ -212,9 +208,6 @@ class MarkdownToDelta extends Converter void visitElementAfter(md.Element element) { final tag = element.tag; - print( - 'Visit after: [tag: ${element.tag}, attributes: ${element.attributes}]'); - if (_isEmbedElement(element)) { _delta.insert(_toEmbeddable(element).toJson()); } diff --git a/lib/src/utils/html2md_utils.dart b/lib/src/utils/html2md_utils.dart index 8f72de2f8..fdc928702 100644 --- a/lib/src/utils/html2md_utils.dart +++ b/lib/src/utils/html2md_utils.dart @@ -6,6 +6,11 @@ import 'package:markdown/src/ast.dart' as ast; import 'package:markdown/src/util.dart' as util; import 'package:meta/meta.dart'; +// [ character +const int $lbracket = 0x5B; +final RegExp youtubeVideoUrlValidator = RegExp( + r'^(?:https?:)?(?:\/\/)?(?:youtu\.be\/|(?:www\.|m\.)?youtube\.com\/(?:watch|v|embed)(?:\.php)?(?:\?.*v=|\/))([a-zA-Z0-9\_-]{7,15})(?:[\?&][a-zA-Z0-9\_-]+=[a-zA-Z0-9\_-]+)*(?:[&\/\#].*)?$'); + ///Local syntax implementation for underline class UnderlineSyntax extends md.DelimiterSyntax { UnderlineSyntax() @@ -17,11 +22,6 @@ class UnderlineSyntax extends md.DelimiterSyntax { ); } -// [ character -const int $lbracket = 0x5B; -final RegExp youtubeVideoUrlValidator = RegExp( - r'^(?:https?:)?(?:\/\/)?(?:youtu\.be\/|(?:www\.|m\.)?youtube\.com\/(?:watch|v|embed)(?:\.php)?(?:\?.*v=|\/))([a-zA-Z0-9\_-]{7,15})(?:[\?&][a-zA-Z0-9\_-]+=[a-zA-Z0-9\_-]+)*(?:[&\/\#].*)?$'); - class VideoSyntax extends md.LinkSyntax { VideoSyntax({super.linkResolver}) : super( diff --git a/test/utils/delta_x_test.dart b/test/utils/delta_x_test.dart index 4ce0dd4b4..092fc8382 100644 --- a/test/utils/delta_x_test.dart +++ b/test/utils/delta_x_test.dart @@ -25,6 +25,11 @@ void main() { Operation.insert('\n'), ]); + final expectedDeltaVideo = Delta.fromOperations([ + Operation.insert({'video': 'https://www.youtube.com/embed/dQw4w9WgXcQ'}), + Operation.insert('\n'), + ]); + test('should detect emphasis and parse correctly', () { final delta = DeltaX.fromHtml(htmlWithEmp); expect(delta, expectedDeltaEmp); @@ -37,6 +42,6 @@ void main() { test('should detect video and parse correctly', () { final delta = DeltaX.fromHtml(htmlWithVideo); - print(delta); + expect(delta, expectedDeltaVideo); }); } From d456066e39bc79722ff37cf08bb4b6a67ade5c4c Mon Sep 17 00:00:00 2001 From: CatHood0 Date: Fri, 28 Jun 2024 15:04:34 -0400 Subject: [PATCH 4/9] removed useless element attr for underline --- lib/src/packages/quill_markdown/markdown_to_delta.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/packages/quill_markdown/markdown_to_delta.dart b/lib/src/packages/quill_markdown/markdown_to_delta.dart index c6870e965..f9103447d 100644 --- a/lib/src/packages/quill_markdown/markdown_to_delta.dart +++ b/lib/src/packages/quill_markdown/markdown_to_delta.dart @@ -83,7 +83,6 @@ class MarkdownToDelta extends Converter final _elementToInlineAttr = { 'em': (_) => [Attribute.italic], 'u': (_) => [Attribute.underline], - 'ins': (_) => [Attribute.underline], 'strong': (_) => [Attribute.bold], 'del': (_) => [Attribute.strikeThrough], 'a': (element) => [LinkAttribute(element.attributes['href'])], From a19338320cf80817ca82f21aa2eb4a4be7b283c1 Mon Sep 17 00:00:00 2001 From: CatHood0 Date: Fri, 28 Jun 2024 15:09:20 -0400 Subject: [PATCH 5/9] improved video url validator pattern --- lib/src/utils/html2md_utils.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/utils/html2md_utils.dart b/lib/src/utils/html2md_utils.dart index fdc928702..14d4f711b 100644 --- a/lib/src/utils/html2md_utils.dart +++ b/lib/src/utils/html2md_utils.dart @@ -9,7 +9,7 @@ import 'package:meta/meta.dart'; // [ character const int $lbracket = 0x5B; final RegExp youtubeVideoUrlValidator = RegExp( - r'^(?:https?:)?(?:\/\/)?(?:youtu\.be\/|(?:www\.|m\.)?youtube\.com\/(?:watch|v|embed)(?:\.php)?(?:\?.*v=|\/))([a-zA-Z0-9\_-]{7,15})(?:[\?&][a-zA-Z0-9\_-]+=[a-zA-Z0-9\_-]+)*(?:[&\/\#].*)?$'); + r'^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$'); ///Local syntax implementation for underline class UnderlineSyntax extends md.DelimiterSyntax { From 5ca57b0d66cb81cf8df73d0bfe96cafe1a65173c Mon Sep 17 00:00:00 2001 From: CatHood0 Date: Fri, 28 Jun 2024 15:47:58 -0400 Subject: [PATCH 6/9] Added support for

This is a normal sentence, and this section has greater underline'; - const htmlWithVideo = + const htmlWithIframeVideo = ''; + + const htmlWithVideoTag = + ''' +'''; final expectedDeltaEmp = Delta.fromOperations([ Operation.insert( 'This is a normal sentence, and this section has greater emp'), @@ -40,8 +44,13 @@ void main() { expect(delta, expectedDeltaUnderline); }); + test('should detect iframe and parse correctly', () { + final delta = DeltaX.fromHtml(htmlWithIframeVideo); + expect(delta, expectedDeltaVideo); + }); + test('should detect video and parse correctly', () { - final delta = DeltaX.fromHtml(htmlWithVideo); + final delta = DeltaX.fromHtml(htmlWithVideoTag); expect(delta, expectedDeltaVideo); }); } From 6ecc86cb32d37c6f7afc85a029cdc48b9830ad98 Mon Sep 17 00:00:00 2001 From: CatHood0 Date: Fri, 28 Jun 2024 15:56:23 -0400 Subject: [PATCH 7/9] chore: dart fixes --- lib/src/models/documents/delta_x.dart | 1 - pubspec.yaml | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/models/documents/delta_x.dart b/lib/src/models/documents/delta_x.dart index eb6777f0c..6939c5881 100644 --- a/lib/src/models/documents/delta_x.dart +++ b/lib/src/models/documents/delta_x.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:html2md/html2md.dart' as html2md; import 'package:markdown/markdown.dart' as md; import 'package:meta/meta.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 6fdbc210d..f764919e2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -75,6 +75,7 @@ dev_dependencies: yaml: ^3.1.2 http: ^1.2.1 + path: any flutter: uses-material-design: true generate: true From e54277d06a6443f5e55d6fab0e1af3fd606627bc Mon Sep 17 00:00:00 2001 From: CatHood0 Date: Fri, 28 Jun 2024 17:08:33 -0400 Subject: [PATCH 8/9] fix: removed useless params --- lib/src/models/documents/delta_x.dart | 68 +++----------- lib/src/utils/delta_x_utils.dart | 84 +++++++++++++++++ lib/src/utils/html2md_utils.dart | 126 -------------------------- 3 files changed, 97 insertions(+), 181 deletions(-) create mode 100644 lib/src/utils/delta_x_utils.dart delete mode 100644 lib/src/utils/html2md_utils.dart diff --git a/lib/src/models/documents/delta_x.dart b/lib/src/models/documents/delta_x.dart index 6939c5881..30d035946 100644 --- a/lib/src/models/documents/delta_x.dart +++ b/lib/src/models/documents/delta_x.dart @@ -3,7 +3,7 @@ import 'package:markdown/markdown.dart' as md; import 'package:meta/meta.dart'; import '../../../markdown_quill.dart'; import '../../../quill_delta.dart'; -import '../../utils/html2md_utils.dart'; +import '../../utils/delta_x_utils.dart'; @immutable @experimental @@ -15,21 +15,12 @@ class DeltaX { /// This api is **experimental** and designed to be used **internally** and shouldn't /// used for **production applications**. @experimental - static Delta fromMarkdown( - String markdownText, { - Md2DeltaConfigs md2DeltaConfigs = const Md2DeltaConfigs(), - }) { + static Delta fromMarkdown(String markdownText) { final mdDocument = md.Document( - encodeHtml: false, inlineSyntaxes: [UnderlineSyntax(), VideoSyntax()]); - final mdToDelta = MarkdownToDelta( - markdownDocument: mdDocument, - customElementToBlockAttribute: - md2DeltaConfigs.customElementToBlockAttribute, - customElementToEmbeddable: md2DeltaConfigs.customElementToEmbeddable, - customElementToInlineAttribute: - md2DeltaConfigs.customElementToInlineAttribute, - softLineBreak: md2DeltaConfigs.softLineBreak, + encodeHtml: false, + inlineSyntaxes: [UnderlineSyntax(), VideoSyntax()], ); + final mdToDelta = MarkdownToDelta(markdownDocument: mdDocument); return mdToDelta.convert(markdownText); } @@ -44,48 +35,15 @@ class DeltaX { /// used for **production applications**. /// @experimental - static Delta fromHtml( - String htmlText, { - Html2MdConfigs? configs, - }) { - configs = Html2MdConfigs( - customRules: [ - Html2MdRules.underlineRule, - Html2MdRules.videoRule, - ...configs?.customRules ?? [] - ], - ignoreIf: configs?.ignoreIf, - rootTag: configs?.rootTag, - imageBaseUrl: configs?.imageBaseUrl, - styleOptions: configs?.styleOptions ?? {'emDelimiter': '*'}, + static Delta fromHtml(String htmlText) { + final markdownText = html2md.convert( + htmlText, + rules: [underlineRule, videoRule], + styleOptions: {'emDelimiter': '*'}, + ).replaceAll( + 'unsafe:', + '', ); - final markdownText = html2md - .convert( - htmlText, - rules: configs.customRules, - ignore: configs.ignoreIf, - rootTag: configs.rootTag, - imageBaseUrl: configs.imageBaseUrl, - styleOptions: configs.styleOptions, - ) - .replaceAll( - 'unsafe:', - '', - ); return fromMarkdown(markdownText); } } - -@experimental -class Md2DeltaConfigs { - const Md2DeltaConfigs({ - this.customElementToInlineAttribute = const {}, - this.customElementToBlockAttribute = const {}, - this.customElementToEmbeddable = const {}, - this.softLineBreak = false, - }); - final Map customElementToInlineAttribute; - final Map customElementToBlockAttribute; - final Map customElementToEmbeddable; - final bool softLineBreak; -} diff --git a/lib/src/utils/delta_x_utils.dart b/lib/src/utils/delta_x_utils.dart new file mode 100644 index 000000000..6cd4689ef --- /dev/null +++ b/lib/src/utils/delta_x_utils.dart @@ -0,0 +1,84 @@ +import 'package:html2md/html2md.dart' as hmd; +import 'package:markdown/markdown.dart' as md; +import 'package:markdown/src/ast.dart' as ast; +import 'package:markdown/src/util.dart' as util; + +// [ character +const int _$lbracket = 0x5B; +final RegExp _youtubeVideoUrlValidator = RegExp( + r'^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$'); + +///Local syntax implementation for underline +class UnderlineSyntax extends md.DelimiterSyntax { + UnderlineSyntax() + : super( + '', + requiresDelimiterRun: true, + allowIntraWord: true, + tags: [md.DelimiterTag('u', 5)], + ); +} + +class VideoSyntax extends md.LinkSyntax { + VideoSyntax({super.linkResolver}) + : super( + pattern: r'\[', + startCharacter: _$lbracket, + ); + + @override + ast.Element createNode( + String destination, + String? title, { + required List Function() getChildren, + }) { + final element = md.Element.empty('video'); + element.attributes['src'] = util.normalizeLinkDestination( + util.escapePunctuation(destination), + ); + if (title != null && title.isNotEmpty) { + element.attributes['title'] = util.normalizeLinkTitle(title); + } + return element; + } +} + +///This rule avoid the default converter from html2md ignore underline tag for or +final underlineRule = + hmd.Rule('underline', filters: ['u', 'ins'], replacement: (content, node) { + //Is used a local underline implemenation since markdown just use underline with html tags + return '$content'; +}); +final videoRule = hmd.Rule('video', filters: ['iframe', 'video'], + replacement: (content, node) { + //This need to be verified by a different way of iframes, since video tag can have children + if (node.nodeName == 'video') { + //if has children then just will be taked as different part of code + if (node.childNum > 0) { + var child = node.firstChild!; + var src = child.getAttribute('src'); + if (src == null) { + child = node.childNodes().last; + src = child.getAttribute('src'); + } + if (!_youtubeVideoUrlValidator.hasMatch(src ?? '')) { + return ''; + } + return '[$content]($src)'; + } + final src = node.getAttribute('src'); + if (src == null || !_youtubeVideoUrlValidator.hasMatch(src)) { + return node.outerHTML; + } + return '[$content]($src)'; + } + //by now, we can only access to src + final src = node.getAttribute('src'); + //if the source is null or is not valid youtube url, then just return the html instead remove it + //by now is only available validation for youtube videos + if (src == null || !_youtubeVideoUrlValidator.hasMatch(src)) { + return node.outerHTML; + } + final title = node.getAttribute('title'); + return '[$title]($src)'; +}); diff --git a/lib/src/utils/html2md_utils.dart b/lib/src/utils/html2md_utils.dart deleted file mode 100644 index 6978dbed0..000000000 --- a/lib/src/utils/html2md_utils.dart +++ /dev/null @@ -1,126 +0,0 @@ -// ignore_for_file: implementation_imports - -import 'package:html2md/html2md.dart' as hmd; -import 'package:markdown/markdown.dart' as md; -import 'package:markdown/src/ast.dart' as ast; -import 'package:markdown/src/util.dart' as util; -import 'package:meta/meta.dart'; - -// [ character -const int $lbracket = 0x5B; -final RegExp youtubeVideoUrlValidator = RegExp( - r'^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$'); - -///Local syntax implementation for underline -class UnderlineSyntax extends md.DelimiterSyntax { - UnderlineSyntax() - : super( - '', - requiresDelimiterRun: true, - allowIntraWord: true, - tags: [md.DelimiterTag('u', 5)], - ); -} - -class VideoSyntax extends md.LinkSyntax { - VideoSyntax({super.linkResolver}) - : super( - pattern: r'\[', - startCharacter: $lbracket, - ); - - @override - ast.Element createNode( - String destination, - String? title, { - required List Function() getChildren, - }) { - final element = md.Element.empty('video'); - element.attributes['src'] = util.normalizeLinkDestination( - util.escapePunctuation(destination), - ); - if (title != null && title.isNotEmpty) { - element.attributes['title'] = util.normalizeLinkTitle(title); - } - return element; - } -} - -class Html2MdRules { - const Html2MdRules._(); - - ///This rule avoid the default converter from html2md ignore underline tag for or - static final underlineRule = hmd.Rule('underline', filters: ['u', 'ins'], - replacement: (content, node) { - //Is used a local underline implemenation since markdown just use underline with html tags - return '$content'; - }); - static final videoRule = hmd.Rule('video', filters: ['iframe', 'video'], - replacement: (content, node) { - //This need to be verified by a different way of iframes, since video tag can have children - if (node.nodeName == 'video') { - //if has children then just will be taked as different part of code - if (node.childNum > 0) { - var child = node.firstChild!; - var src = child.getAttribute('src'); - if (src == null) { - child = node.childNodes().last; - src = child.getAttribute('src'); - } - if (!youtubeVideoUrlValidator.hasMatch(src ?? '')) { - return ''; - } - return '[$content]($src)'; - } - final src = node.getAttribute('src'); - if (src == null || !youtubeVideoUrlValidator.hasMatch(src)) { - return node.outerHTML; - } - return '[$content]($src)'; - } - //by now, we can only access to src - final src = node.getAttribute('src'); - //if the source is null or is not valid youtube url, then just return the html instead remove it - //by now is only available validation for youtube videos - if (src == null || !youtubeVideoUrlValidator.hasMatch(src)) { - return node.outerHTML; - } - final title = node.getAttribute('title'); - return '[$title]($src)'; - }); -} - -@experimental -class Html2MdConfigs { - const Html2MdConfigs({ - this.customRules, - this.ignoreIf, - this.rootTag, - this.imageBaseUrl, - this.styleOptions, - }); - - /// The [rules] parameter can be used to customize element processing. - final List? customRules; - - /// Elements list in [ignore] would be ingored. - final List? ignoreIf; - - final String? rootTag; - final String? imageBaseUrl; - - /// The default and available style options: - /// - /// | Name | Default | Options | - /// | ------------- |:-------------:| -----:| - /// | headingStyle | "setext" | "setext", "atx" | - /// | hr | "* * *" | "* * *", "- - -", "_ _ _" | - /// | bulletListMarker | "*" | "*", "-", "_" | - /// | codeBlockStyle | "indented" | "indented", "fenced" | - /// | fence | "\`\`\`" | "\`\`\`", "~~~" | - /// | emDelimiter | "_" | "_", "*" | - /// | strongDelimiter | "**" | "**", "__" | - /// | linkStyle | "inlined" | "inlined", "referenced" | - /// | linkReferenceStyle | "full" | "full", "collapsed", "shortcut" | - final Map? styleOptions; -} From acf633690aa5b8730efb7eab246125f118520885 Mon Sep 17 00:00:00 2001 From: CatHood0 Date: Fri, 28 Jun 2024 17:54:30 -0400 Subject: [PATCH 9/9] fix: imports issue --- lib/src/utils/delta_x_utils.dart | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/src/utils/delta_x_utils.dart b/lib/src/utils/delta_x_utils.dart index 6cd4689ef..b7767b1e6 100644 --- a/lib/src/utils/delta_x_utils.dart +++ b/lib/src/utils/delta_x_utils.dart @@ -1,7 +1,5 @@ import 'package:html2md/html2md.dart' as hmd; import 'package:markdown/markdown.dart' as md; -import 'package:markdown/src/ast.dart' as ast; -import 'package:markdown/src/util.dart' as util; // [ character const int _$lbracket = 0x5B; @@ -27,17 +25,15 @@ class VideoSyntax extends md.LinkSyntax { ); @override - ast.Element createNode( + md.Element createNode( String destination, String? title, { - required List Function() getChildren, + required List Function() getChildren, }) { final element = md.Element.empty('video'); - element.attributes['src'] = util.normalizeLinkDestination( - util.escapePunctuation(destination), - ); + element.attributes['src'] = destination; if (title != null && title.isNotEmpty) { - element.attributes['title'] = util.normalizeLinkTitle(title); + element.attributes['title'] = title; } return element; }