|
|
|
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,
|
|
|
|
OpAttributeSanitizerOptions,
|
|
|
|
OpConverterOptions,
|
|
|
|
InlineStyles,
|
|
|
|
InlineStyleType,
|
|
|
|
defaultInlineFonts;
|
|
|
|
|
|
|
|
typedef ConverterOptions = converter.ConverterOptions;
|
|
|
|
|
|
|
|
/// A extension for [Delta] which comes from `flutter_quill` to extends
|
|
|
|
/// the functionality of it to support converting the [Delta] to/from HTML
|
|
|
|
extension DeltaHtmlExt on Delta {
|
|
|
|
/// Convert the [Delta] instance to HTML Raw string
|
|
|
|
///
|
|
|
|
/// It will run using the following steps:
|
|
|
|
///
|
|
|
|
/// 1. Convert the [Delta] to json using [toJson]
|
|
|
|
/// 2. Cast the json map as `List<Map<String, dynamic>>`
|
|
|
|
/// 3. Pass it to the conventer `vsc_quill_delta_to_html` which is a package
|
|
|
|
/// that designed specifically for converting the quill delta to html
|
|
|
|
String toHtml({ConverterOptions? options}) {
|
|
|
|
final json = toJson();
|
|
|
|
final html = converter.QuillDeltaToHtmlConverter(
|
|
|
|
List.castFrom(json),
|
|
|
|
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:
|
|
|
|
// <p>hello</p> -> isn't created the attribute
|
|
|
|
//
|
|
|
|
// But, with this validation an implementation of the style will be:
|
|
|
|
// <p><span style="line-height: 1.5px">hello</span></p>
|
|
|
|
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(
|
|
|
|
<String, converter.InlineStyleType>{
|
|
|
|
'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: <String, String>{
|
|
|
|
// 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;",
|
|
|
|
},
|
|
|
|
),
|
|
|
|
},
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|