singerdmx / flutter-quill

Rich text editor for Flutter
https://pub.dev/packages/flutter_quill
MIT License
2.43k stars 770 forks source link

Style is not saved or parsed well when i get back to the quill #1967

Closed TamirYakov1 closed 23 hours ago

TamirYakov1 commented 4 days ago

Is there an existing issue for this?

The question

How can i parse the text to html (for server side use) and back from html so i will get it styled on the quill?

Flutter Quill version 9.3.3

Steps to reproduce

  1. Write and style (color/font size/underline etc)
  2. Use toHtml on save
  3. Move to other page
  4. Go back and parse the html from the saved state

Expected results The text will be styled the same as i left it

Actual results The text is not styled expect italic and bold and bullets

Code sample

`import 'dart:convert';

import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_quill/flutter_quill.dart'; import 'package:quill_html_converter/quill_html_converter.dart'; import 'package:sviva_visits/common/extensions/extensions.dart'; import 'package:sviva_visits/common/widgets/conditional_parent.dart'; import 'package:sviva_visits/common/widgets/custom_fa_icon.dart'; import 'package:sviva_visits/common/widgets/gap.dart'; import 'package:sviva_visits/core/app_config/app_config.dart'; import 'package:sviva_visits/core/app_config/app_font_awesome_icons.dart'; import 'package:vsc_quill_delta_to_html/vsc_quill_delta_to_html.dart' as vsc;

class AppTextEditor extends StatefulWidget { final ValueSetter onTextChanged; final ValueSetter? onCharCountUpdate; final int? plainCharLimit; final Function()? onEditorCreated;

// final QuillEditorController? controller; final bool isSmallEditor; final String? labelText; final double? height; final String? supportingText; final TextStyle? supportingTextStyle; final double? supportingTextPaddingTop; final double? supportingTextHeight; final Widget? supportingExtraWidget; final String? Function(String?)? validator; final String? error; final String? hintText; final bool isError; final String? initialValue; final bool isFieldsEnabled; final bool isLocked; final Function()? onFocusChange; final bool expands;

const AppTextEditor({ super.key, required this.onTextChanged, this.onCharCountUpdate, this.plainCharLimit, this.onEditorCreated, // this.controller, this.isSmallEditor = false, this.labelText, this.height, this.supportingTextPaddingTop, this.supportingText, this.supportingExtraWidget, this.supportingTextHeight, this.supportingTextStyle, this.error, this.validator, this.hintText, this.isError = false, this.initialValue, this.isFieldsEnabled = true, this.isLocked = false, this.onFocusChange, this.expands = false, });

@override State createState() => _AppTextEditorState(); }

class _AppTextEditorState extends State { late QuillController controller; late FocusNode focusNode;

@override void initState() { super.initState(); controller = QuillController.basic(); focusNode = FocusNode(); WidgetsBinding.instance.addPostFrameCallback((_) { final html = controller.document.toDelta().toHtml( options: ConverterOptions( converterOptions: vsc.OpConverterOptions( inlineStylesFlag: true, allowBackgroundClasses: true), ), ); final plainTextLength = controller.document .getPlainText(0, controller.document.length - 1) .replaceAll("\n", "") .length; widget.onCharCountUpdate?.call(plainTextLength); widget.onTextChanged(html); }); setupInitialText(); }

void setupInitialText() { final initialData = widget.initialValue ?? ''; if (initialData.isNotEmpty && initialData != "


") { try { controller.document = Document.fromHtml(initialData); } catch (e) { final decodedJson = jsonDecode('{"insert":"$initialData\n"}'); controller.document = Document.fromJson([decodedJson]); } } controller.document.changes.listen((event) { if (event.change.length > 0) { WidgetsBinding.instance.addPostFrameCallback((_) { final plainTextLength = controller.document .getPlainText(0, controller.document.length - 1) .replaceAll("\n", "") .length; final charLimit = widget.plainCharLimit; if (charLimit != null && plainTextLength > charLimit) { controller.undo(); return; } else { widget.onTextChanged(controller.document.toDelta().toHtml( options: ConverterOptions( converterOptions: vsc.OpConverterOptions( inlineStylesFlag: true, allowBackgroundClasses: true), ), )); widget.onCharCountUpdate?.call(plainTextLength); } }); } }); }

// @override // void didUpdateWidget(covariant AppTextEditor oldWidget) { // super.didUpdateWidget(oldWidget); // if (widget.initialValue != oldWidget.initialValue) { // setupInitialText(); // } // }

@override Widget build(BuildContext context) { return ConditionalParent( parentBuilder: (Widget child) { return IgnorePointer( child: Stack( alignment: Alignment.center, children: [ child, const Positioned( left: 20, child: Padding( padding: EdgeInsets.only(top: 20.0), child: CustomFaIcon( iconUnicode: AppFontAwesomeIcons.penSlashUnicode, fontSize: 16, color: AppColors.hintColor, ), ), ), ], ), ); }, condition: !widget.isFieldsEnabled, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (widget.labelText != null) Padding( padding: const EdgeInsets.symmetric(horizontal: 14), child: Text.rich( TextSpan(children: [ if (widget.validator != null) const WidgetSpan( child: Text( '*', style: TextStyle( color: Colors.red, fontSize: 14, ), ), ), const WidgetSpan(child: Gap.widthW(3)), WidgetSpan( child: Text( widget.labelText!, style: const TextStyle( fontSize: 14, color: AppColors.primaryBlack, fontWeight: FontWeight.w400, ), ), ), ]), ), ), Container( decoration: BoxDecoration( border: Border.all( width: 1, color: widget.error != null ? AppColors.errorColor : AppColors.gray, ), borderRadius: BorderRadius.circular(8), ), child: ClipRRect( borderRadius: BorderRadius.circular(8), child: Column( mainAxisSize: MainAxisSize.min, children: [ QuillToolbar.simple( configurations: QuillSimpleToolbarConfigurations( showAlignmentButtons: true, showDirection: true, controller: controller, sharedConfigurations: const QuillSharedConfigurations( locale: Locale('he')))), GestureDetector( onTap: () { focusNode.requestFocus(); }, child: Container( color: widget.isLocked ? AppColors.disabledInputGray : Colors.white, height: widget.expands ? null : widget.isSmallEditor ? 100 : widget.height ?? 300, padding: const EdgeInsets.all(20), child: FocusScope( onFocusChange: (isFocusedChanged) { if (widget.onFocusChange != null && focusNode.hasPrimaryFocus) { widget.onFocusChange!(); } }, child: QuillEditor.basic( focusNode: focusNode, configurations: QuillEditorConfigurations( placeholder: widget.hintText, controller: controller, customStyles: DefaultStyles( placeHolder: DefaultTextBlockStyle( AppTextStyles.mainNormalHintTextStyle( fontSize: 25.sp(context), color: AppColors.hintColor .withOpacity(0.7), context), VerticalSpacing(0, 0), VerticalSpacing(0, 0), null)), onTapOutside: (p, _) => focusNode.unfocus(), minHeight: widget.expands ? (widget.isSmallEditor ? 100 : widget.height ?? 300) : null, // expands: widget.expands, sharedConfigurations: const QuillSharedConfigurations( locale: Locale('he')))), )), ) ], ), ), ), if (widget.isSmallEditor || widget.error != null) SizedBox( height: widget.supportingTextHeight ?? 20.h(context), child: widget.supportingText == null && widget.error == null && widget.validator == null ? SizedBox( height: 16.h(context), ) : Padding( padding: EdgeInsets.only( left: calcWidth(16, context), right: calcWidth(16, context), top: widget.supportingTextPaddingTop ?? 0, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( widget.validator != null ? widget.error ?? getError(widget.initialValue ?? '') ?? '' : widget.error ?? widget.supportingText ?? '', maxLines: 1, overflow: TextOverflow.ellipsis, style: widget.error != null || widget.validator != null ? AppTextStyles.errorTextStyle(context) : widget.supportingTextStyle ?? AppTextStyles.mainSupportingTextStyle( context), ), if (widget.supportingExtraWidget != null) widget.supportingExtraWidget!, ], ), ), ), ], ), ); }

String? getError(String content) { return (widget.validator != null && widget.isError ? widget.validator!(content) : null); } } `

CatHood0 commented 4 days ago

Can you provide any screenshot or video? By now most of the common html tags are supported with attributes (just the ones that are registered on the package)

CatHood0 commented 23 hours ago

Since there aren't any response, i close this. This issue is already solved since we use flutter_quill_delta_from_html package