diff --git a/quill_html_converter/lib/quill_html_converter.dart b/quill_html_converter/lib/quill_html_converter.dart index 460b74dc..0075bcb5 100644 --- a/quill_html_converter/lib/quill_html_converter.dart +++ b/quill_html_converter/lib/quill_html_converter.dart @@ -2,7 +2,15 @@ library quill_html_converter; import 'package:dart_quill_delta/dart_quill_delta.dart'; import 'package:vsc_quill_delta_to_html/vsc_quill_delta_to_html.dart' - as converter show ConverterOptions, QuillDeltaToHtmlConverter; + as converter + show + ConverterOptions, + QuillDeltaToHtmlConverter, + OpAttributeSanitizerOptions, + OpConverterOptions, + InlineStyles, + InlineStyleType, + defaultInlineFonts; typedef ConverterOptions = converter.ConverterOptions; @@ -21,8 +29,103 @@ extension DeltaHtmlExt on Delta { final json = toJson(); final html = converter.QuillDeltaToHtmlConverter( List.castFrom(json), - options, + options ?? _defaultConverterOptions, ).convert(); return html; } } + +/// Configuration options for converting Quill Delta to HTML. +/// This includes various settings for how different elements and styles should be handled. +final _defaultConverterOptions = ConverterOptions( + // Tag to be used for ordered lists + orderedListTag: 'ol', + + // Tag to be used for bullet lists + bulletListTag: 'ul', + + // Enable multi-line blockquote conversion + multiLineBlockquote: true, + + // Enable multi-line header conversion + multiLineHeader: true, + + // Enable multi-line code block conversion + multiLineCodeblock: true, + + // Enable multi-line paragraph conversion + multiLineParagraph: true, + + // Enable multi-line custom block conversion + multiLineCustomBlock: true, + + // Options for sanitizing attributes + sanitizerOptions: converter.OpAttributeSanitizerOptions( + // Allow 8-digit hex colors in styles + allow8DigitHexColors: true, + ), + + // This handle specific styles and attributes + converterOptions: converter.OpConverterOptions( + customCssStyles: (op) { + // Validate if our attributes exist in [DeltaInsertOp] + // and return the necessary HTML style + // These lists of attributes, are passed as inline styles + // + // For example, if you have a delta like -> + // [ { "insert": "hello", "attributes": { "line-height": 1.5 }} ] + // + // Without the validation below to verify if exist line-height atribute in the Operation, it would be: + //

hello

-> isn't created the attribute + // + // But, with this validation an implementation of the style will be: + //

hello

+ if (op.attributes['line-height'] != null) { + return ['line-height: ${op.attributes['line-height']}px']; + } + if (op.isImage()) { + // Fit images within restricted parent width + final String? styles = op.attributes['style']; + final listStyles = styles?.split(';') ?? []; + return ['max-width: 100%', 'object-fit: contain', ...listStyles]; + } + return null; + }, + // Enable inline styles + inlineStylesFlag: true, + inlineStyles: converter.InlineStyles( + { + 'font': converter.InlineStyleType( + fn: (value, _) => + converter.defaultInlineFonts[value] ?? 'font-family: $value', + ), + 'size': converter.InlineStyleType( + fn: (value, _) { + // Default sizes + if (value == 'small') return 'font-size: 0.75em'; + if (value == 'large') return 'font-size: 1.5em'; + if (value == 'huge') return 'font-size: 2.5em'; + // Accept any int or double type size + return 'font-size: ${value}px'; + }, + ), + 'indent': converter.InlineStyleType( + fn: (value, op) { + // Calculate indent size based on the value + final indentSize = (double.tryParse(value) ?? double.nan) * 3; + // Determine side for padding based on text direction + final side = op.attributes['direction'] == 'rtl' ? 'right' : 'left'; + return 'padding-$side:${indentSize}em'; + }, + ), + 'list': converter.InlineStyleType( + map: { + // Styles for checked and unchecked list items + 'checked': "list-style-type:'\\2611';padding-left: 0.5em;", + 'unchecked': "list-style-type:'\\2610';padding-left: 0.5em;", + }, + ), + }, + ), + ), +); diff --git a/quill_html_converter/test/quill_html_converter_test.dart b/quill_html_converter/test/quill_html_converter_test.dart index 3be46c15..7996c696 100644 --- a/quill_html_converter/test/quill_html_converter_test.dart +++ b/quill_html_converter/test/quill_html_converter_test.dart @@ -4,7 +4,7 @@ import 'package:test/test.dart'; void main() { group('Quill HTML Converter', () { - test('Example of toHtml', () { + test('should parser delta heading to html', () { const html = '



'; final quillDelta = [ {'insert': '\n'}, @@ -15,5 +15,33 @@ void main() { ]; expect(Delta.fromJson(quillDelta).toHtml().trim(), html.trim()); }); + + test('should parse line-height attribute to html', () { + const html = '

hello

'; + final quillDelta = [ + { + 'insert': 'hello', + 'attributes': {'line-height': 1.5} + }, + {'insert': '\n'} + ]; + expect(Delta.fromJson(quillDelta).toHtml().trim(), html.trim()); + }); + + test("should parse block image embed with it's attributes to html", () { + const html = + '

'; + final quillDelta = [ + { + 'insert': { + 'image': + 'https://img.freepik.com/foto-gratis/belleza-otonal-abstracta-patron-venas-hoja-multicolor-generado-ia_188544-9871.jpg' + }, + 'attributes': {'style': 'width: 40vh; height:350px; margin: 20px;'} + }, + {'insert': '\n'} + ]; + expect(Delta.fromJson(quillDelta).toHtml().trim(), html.trim()); + }); }); }