430 lines
16 KiB
Dart
430 lines
16 KiB
Dart
|
import 'dart:async';
|
||
|
import 'dart:collection';
|
||
|
|
||
|
import 'package:file_picker/file_picker.dart';
|
||
|
import 'package:flutter/material.dart';
|
||
|
import 'package:html_editor_enhanced/html_editor.dart';
|
||
|
|
||
|
/// Options that modify the editor and its behavior
|
||
|
class HtmlEditorOptions {
|
||
|
const HtmlEditorOptions({
|
||
|
this.autoAdjustHeight = true,
|
||
|
this.androidUseHybridComposition = true,
|
||
|
this.adjustHeightForKeyboard = true,
|
||
|
this.characterLimit,
|
||
|
this.customOptions = '',
|
||
|
this.darkMode,
|
||
|
this.disabled = false,
|
||
|
this.filePath,
|
||
|
this.hint,
|
||
|
this.initialText,
|
||
|
this.inputType = HtmlInputType.text,
|
||
|
this.mobileContextMenu,
|
||
|
this.mobileLongPressDuration,
|
||
|
this.mobileInitialScripts,
|
||
|
this.webInitialScripts,
|
||
|
this.shouldEnsureVisible = false,
|
||
|
this.spellCheck = false,
|
||
|
});
|
||
|
|
||
|
/// The editor will automatically adjust its height when the keyboard is active
|
||
|
/// to prevent the keyboard overlapping the editor.
|
||
|
///
|
||
|
/// The default value is true. It is recommended to leave this as true because
|
||
|
/// it significantly improves the UX.
|
||
|
final bool adjustHeightForKeyboard;
|
||
|
|
||
|
/// ALlows devs to set hybrid composition off in case they would like to
|
||
|
/// prioritize animation smoothness over text input experience.
|
||
|
///
|
||
|
/// The recommended value is `true`.
|
||
|
final bool androidUseHybridComposition;
|
||
|
|
||
|
/// The editor will automatically adjust its height once the page is loaded to
|
||
|
/// ensure there is no vertical scrolling or empty space. It will only perform
|
||
|
/// the adjustment when the summernote editor is the loaded page.
|
||
|
///
|
||
|
/// It will also disable vertical scrolling on the webview, so scrolling on
|
||
|
/// the webview will actually scroll the rest of the page rather than doing
|
||
|
/// nothing because it is trying to scroll the webview container.
|
||
|
///
|
||
|
/// The default value is true. It is recommended to leave this as true because
|
||
|
/// it significantly improves the UX.
|
||
|
final bool autoAdjustHeight;
|
||
|
|
||
|
/// Adds a character limit to the editor.
|
||
|
///
|
||
|
/// NOTE: ONLY WORKS ON iOS AND WEB PLATFORMS!!
|
||
|
final int? characterLimit;
|
||
|
|
||
|
/// Set custom options for the summernote editor by using their syntax.
|
||
|
///
|
||
|
/// Please ensure your syntax is correct (and add a comma at the end of your
|
||
|
/// string!) otherwise the editor may not load.
|
||
|
final String customOptions;
|
||
|
|
||
|
/// Sets the editor to dark mode. `null` - switches with system, `false` -
|
||
|
/// always light, `true` - always dark.
|
||
|
///
|
||
|
/// The default value is null (switches with system).
|
||
|
final bool? darkMode;
|
||
|
|
||
|
/// Disable the editor immediately after startup. You can re-enable the editor
|
||
|
/// by calling [controller.enable()].
|
||
|
final bool disabled;
|
||
|
|
||
|
/// Specify the file path to your custom html editor code.
|
||
|
///
|
||
|
/// Make sure to set the editor's HTML ID to be 'summernote-2'.
|
||
|
///
|
||
|
/// If you plan to use this on Web, you must add comments in your HTML so the
|
||
|
/// package can insert the relevant JS code to communicate between Dart and JS.
|
||
|
/// See the README for more details on this.
|
||
|
final String? filePath;
|
||
|
|
||
|
/// Sets the Html editor's hint (text displayed when there is no text in the
|
||
|
/// editor).
|
||
|
final String? hint;
|
||
|
|
||
|
/// The initial text that is be supplied to the Html editor.
|
||
|
final String? initialText;
|
||
|
|
||
|
/// Changes the display of the virtual keyboard on mobile devices.
|
||
|
///
|
||
|
/// See [HtmlInputType] for the supported modes.
|
||
|
///
|
||
|
/// The default value is [HtmlInputType.text] (the standard virtual keyboard)
|
||
|
final HtmlInputType inputType;
|
||
|
|
||
|
/// Customize the context menu for selected text on mobile
|
||
|
final ContextMenu? mobileContextMenu;
|
||
|
|
||
|
/// Set the duration until a long-press is recognized.
|
||
|
///
|
||
|
/// The default value is 500ms.
|
||
|
final Duration? mobileLongPressDuration;
|
||
|
|
||
|
/// Initial JS to inject into the editor.
|
||
|
final UnmodifiableListView<UserScript>? mobileInitialScripts;
|
||
|
|
||
|
/// Initial JS to add to the editor. These can be called at any time using
|
||
|
/// [controller.evaluateJavascriptWeb]
|
||
|
final UnmodifiableListView<WebScript>? webInitialScripts;
|
||
|
|
||
|
/// Specifies whether the widget should scroll to reveal the HTML editor when
|
||
|
/// it is focused or the text content is changed.
|
||
|
/// See the README examples for the best way to implement this.
|
||
|
///
|
||
|
/// Note: Your editor *must* be in a Scrollable type widget (e.g. ListView,
|
||
|
/// SingleChildScrollView, etc.) for this to work. Otherwise, nothing will
|
||
|
/// happen.
|
||
|
final bool shouldEnsureVisible;
|
||
|
|
||
|
/// Specify whether or not the editor should spellcheck its contents.
|
||
|
///
|
||
|
/// Default value is false.
|
||
|
final bool spellCheck;
|
||
|
}
|
||
|
|
||
|
/// Options that modify the toolbar and its behavior
|
||
|
class HtmlToolbarOptions {
|
||
|
const HtmlToolbarOptions({
|
||
|
this.audioExtensions,
|
||
|
this.customToolbarButtons = const [],
|
||
|
this.customToolbarInsertionIndices = const [],
|
||
|
this.defaultToolbarButtons = const [
|
||
|
StyleButtons(),
|
||
|
FontSettingButtons(fontSizeUnit: false),
|
||
|
FontButtons(clearAll: false),
|
||
|
ColorButtons(),
|
||
|
ListButtons(listStyles: false),
|
||
|
ParagraphButtons(
|
||
|
textDirection: false, lineHeight: false, caseConverter: false),
|
||
|
InsertButtons(
|
||
|
video: false,
|
||
|
audio: false,
|
||
|
table: false,
|
||
|
hr: false,
|
||
|
otherFile: false),
|
||
|
],
|
||
|
this.otherFileExtensions,
|
||
|
this.imageExtensions,
|
||
|
this.initiallyExpanded = false,
|
||
|
this.linkInsertInterceptor,
|
||
|
this.mediaLinkInsertInterceptor,
|
||
|
this.mediaUploadInterceptor,
|
||
|
this.onButtonPressed,
|
||
|
this.onDropdownChanged,
|
||
|
this.onOtherFileLinkInsert,
|
||
|
this.onOtherFileUpload,
|
||
|
this.toolbarType = ToolbarType.nativeScrollable,
|
||
|
this.toolbarPosition = ToolbarPosition.aboveEditor,
|
||
|
this.videoExtensions,
|
||
|
this.dropdownElevation = 8,
|
||
|
this.dropdownIcon,
|
||
|
this.dropdownIconColor,
|
||
|
this.dropdownIconSize = 24,
|
||
|
this.dropdownItemHeight = kMinInteractiveDimension,
|
||
|
this.dropdownFocusColor,
|
||
|
this.dropdownBackgroundColor,
|
||
|
this.dropdownMenuDirection,
|
||
|
this.dropdownMenuMaxHeight,
|
||
|
this.dropdownBoxDecoration,
|
||
|
this.buttonColor,
|
||
|
this.buttonSelectedColor,
|
||
|
this.buttonFillColor,
|
||
|
this.buttonFocusColor,
|
||
|
this.buttonHighlightColor,
|
||
|
this.buttonHoverColor,
|
||
|
this.buttonSplashColor,
|
||
|
this.buttonBorderColor,
|
||
|
this.buttonSelectedBorderColor,
|
||
|
this.buttonBorderRadius,
|
||
|
this.buttonBorderWidth,
|
||
|
this.renderBorder = false,
|
||
|
this.textStyle,
|
||
|
this.separatorWidget =
|
||
|
const VerticalDivider(indent: 2, endIndent: 2, color: Colors.grey),
|
||
|
this.renderSeparatorWidget = true,
|
||
|
this.toolbarItemHeight = 36,
|
||
|
this.gridViewHorizontalSpacing = 5,
|
||
|
this.gridViewVerticalSpacing = 5,
|
||
|
});
|
||
|
|
||
|
/// Allows you to set the allowed extensions when a user inserts an audio file
|
||
|
///
|
||
|
/// By default any audio extension is allowed.
|
||
|
final List<String>? audioExtensions;
|
||
|
|
||
|
/// Allows you to create your own buttons that are added to the end of the
|
||
|
/// default buttons list
|
||
|
final List<Widget> customToolbarButtons;
|
||
|
|
||
|
/// Allows you to set where each custom toolbar button is inserted into the
|
||
|
/// toolbar buttons.
|
||
|
///
|
||
|
/// Notes: 1) This list should have the same length as the [customToolbarButtons]
|
||
|
///
|
||
|
/// 2) If any indices > [defaultToolbarButtons.length] then the plugin will
|
||
|
/// automatically account for this and insert the buttons at the end of the
|
||
|
/// [defaultToolbarButtons]
|
||
|
///
|
||
|
/// 3) If any indices < 0 then the plugin will automatically account for this
|
||
|
/// and insert the buttons at the beginning of the [defaultToolbarButtons]
|
||
|
final List<int> customToolbarInsertionIndices;
|
||
|
|
||
|
/// Sets which options are visible in the toolbar for the editor.
|
||
|
final List<Toolbar> defaultToolbarButtons;
|
||
|
|
||
|
/// Allows you to set the allowed extensions when a user inserts an image
|
||
|
///
|
||
|
/// By default any image extension is allowed.
|
||
|
final List<String>? imageExtensions;
|
||
|
|
||
|
/// Allows you to set whether the toolbar starts out expanded (in gridview)
|
||
|
/// or contracted (in scrollview).
|
||
|
///
|
||
|
/// By default it starts out contracted.
|
||
|
///
|
||
|
/// This option only works when you have set [toolbarType] to
|
||
|
/// [ToolbarType.nativeExpandable].
|
||
|
final bool initiallyExpanded;
|
||
|
|
||
|
/// Allows you to intercept any links being inserted into the editor. The
|
||
|
/// function passes the display text, the URL itself, and whether the
|
||
|
/// URL should open a new tab.
|
||
|
///
|
||
|
/// Return a bool to tell the plugin if it should continue with its own handler
|
||
|
/// or if you want to handle the link by yourself.
|
||
|
/// (true = continue with internal handler, false = do not use internal handler)
|
||
|
///
|
||
|
/// If no interceptor is set, the plugin uses the internal handler.
|
||
|
final FutureOr<bool> Function(String, String, bool)? linkInsertInterceptor;
|
||
|
|
||
|
/// Allows you to intercept any image/video/audio inserted as a link into the editor.
|
||
|
/// The function passes the URL of the media inserted.
|
||
|
///
|
||
|
/// Return a bool to tell the plugin if it should continue with its own handler
|
||
|
/// or if you want to handle the image/video link by yourself.
|
||
|
/// (true = continue with internal handler, false = do not use internal handler)
|
||
|
///
|
||
|
/// If no interceptor is set, the plugin uses the internal handler.
|
||
|
final FutureOr<bool> Function(String, InsertFileType)?
|
||
|
mediaLinkInsertInterceptor;
|
||
|
|
||
|
/// Allows you to intercept any image/video/audio files being inserted into the editor.
|
||
|
/// The function passes the PlatformFile class, which contains all the file data
|
||
|
/// including name, size, type, Uint8List bytes, etc.
|
||
|
///
|
||
|
/// Return a bool to tell the plugin if it should continue with its own handler
|
||
|
/// or if you want to handle the image/video/audio upload by yourself.
|
||
|
/// (true = continue with internal handler, false = do not use internal handler)
|
||
|
///
|
||
|
/// If no interceptor is set, the plugin uses the internal handler.
|
||
|
final FutureOr<bool> Function(PlatformFile, InsertFileType)?
|
||
|
mediaUploadInterceptor;
|
||
|
|
||
|
/// Allows you to intercept any button press. The function passes the ButtonType
|
||
|
/// enum, which tells you which button was pressed, the current selected status of
|
||
|
/// the button, and a function to reverse the status (in case you decide to handle
|
||
|
/// the button press yourself).
|
||
|
///
|
||
|
/// Note: In some cases, the button is never active (e.g. copy/paste buttons)
|
||
|
/// so null will be returned for both the selected status and the function.
|
||
|
///
|
||
|
/// Return a bool to tell the plugin if it should continue with its own handler
|
||
|
/// or if you want to handle the button press by yourself.
|
||
|
/// (true = continue with internal handler, false = do not use internal handler)
|
||
|
///
|
||
|
/// If no interceptor is set, the plugin uses the internal handler.
|
||
|
final FutureOr<bool> Function(ButtonType, bool?, Function?)? onButtonPressed;
|
||
|
|
||
|
/// Allows you to intercept any dropdown changes. The function passes the
|
||
|
/// DropdownType enum, which tells you which dropdown was changed,
|
||
|
/// the changed value to indicate what the dropdown was changed to, and the
|
||
|
/// function to update the changed value (in case you decide to handle the
|
||
|
/// dropdown change yourself). The function is null in some cases because
|
||
|
/// the dropdown does not update its value.
|
||
|
///
|
||
|
/// Return a bool to tell the plugin if it should continue with its own handler
|
||
|
/// or if you want to handle the dropdown change by yourself.
|
||
|
/// (true = continue with internal handler, false = do not use internal handler)
|
||
|
///
|
||
|
/// If no interceptor is set, the plugin uses the internal handler.
|
||
|
final FutureOr<bool> Function(DropdownType, dynamic, void Function(dynamic)?)?
|
||
|
onDropdownChanged;
|
||
|
|
||
|
/// Called when a link is inserted for a file using the "other file" button.
|
||
|
///
|
||
|
/// The package does not have a built in handler for these files, so you should
|
||
|
/// provide this callback when using the button.
|
||
|
///
|
||
|
/// The function passes the URL of the file inserted.
|
||
|
final void Function(String)? onOtherFileLinkInsert;
|
||
|
|
||
|
/// Called when a file is uploaded using the "other file" button.
|
||
|
///
|
||
|
/// The package does not have a built in handler for these files, so if you use
|
||
|
/// the button you should provide this callback.
|
||
|
///
|
||
|
/// The function passes the PlatformFile class, which contains all the file data
|
||
|
/// including name, size, type, Uint8List bytes, etc.
|
||
|
final void Function(PlatformFile)? onOtherFileUpload;
|
||
|
|
||
|
/// Allows you to set the allowed extensions when a user inserts a file other
|
||
|
/// than image/audio/video
|
||
|
///
|
||
|
/// By default any other extension is allowed.
|
||
|
final List<String>? otherFileExtensions;
|
||
|
|
||
|
/// Controls how the toolbar displays. See [ToolbarType] for more details.
|
||
|
///
|
||
|
/// By default the toolbar is rendered as a scrollable one-line list.
|
||
|
final ToolbarType toolbarType;
|
||
|
|
||
|
/// Controls where the toolbar is positioned. See [ToolbarPosition] for more details.
|
||
|
///
|
||
|
/// By default the toolbar is above the editor.
|
||
|
final ToolbarPosition toolbarPosition;
|
||
|
|
||
|
/// Allows you to set the allowed extensions when a user inserts a video.
|
||
|
///
|
||
|
/// By default any video extension is allowed.
|
||
|
final List<String>? videoExtensions;
|
||
|
|
||
|
/// Styling options for the toolbar:
|
||
|
|
||
|
/// Determines whether a border is rendered around all toolbar widgets
|
||
|
///
|
||
|
/// The default value is false. True is recommended for [ToolbarType.nativeGrid].
|
||
|
final bool renderBorder;
|
||
|
|
||
|
/// Sets the text style for all toolbar widgets
|
||
|
final TextStyle? textStyle;
|
||
|
|
||
|
/// Sets the separator widget between toolbar sections. This widget is only
|
||
|
/// used in [ToolbarType.nativeScrollable].
|
||
|
///
|
||
|
/// The default widget is [VerticalDivider(indent: 2, endIndent: 2, color: Colors.grey)]
|
||
|
final Widget separatorWidget;
|
||
|
|
||
|
/// Determines whether the separator widget is rendered
|
||
|
///
|
||
|
/// The default value is true
|
||
|
final bool renderSeparatorWidget;
|
||
|
|
||
|
/// Sets the height of the toolbar items
|
||
|
///
|
||
|
/// Button width is affected by this parameter, however dropdown widths are
|
||
|
/// not affected. The plugin will maintain a square shape for all buttons.
|
||
|
///
|
||
|
/// The default value is 36
|
||
|
final double toolbarItemHeight;
|
||
|
|
||
|
/// Sets the vertical spacing between rows when using [ToolbarType.nativeGrid]
|
||
|
///
|
||
|
/// The default value is 5
|
||
|
final double gridViewVerticalSpacing;
|
||
|
|
||
|
/// Sets the horizontal spacing between items when using [ToolbarType.nativeGrid]
|
||
|
///
|
||
|
/// The default value is 5
|
||
|
final double gridViewHorizontalSpacing;
|
||
|
|
||
|
/// Styling options that only apply to dropdowns:
|
||
|
/// (See the [DropdownButton] class for more information)
|
||
|
|
||
|
final int dropdownElevation;
|
||
|
final Widget? dropdownIcon;
|
||
|
final Color? dropdownIconColor;
|
||
|
final double dropdownIconSize;
|
||
|
final double dropdownItemHeight;
|
||
|
final Color? dropdownFocusColor;
|
||
|
final Color? dropdownBackgroundColor;
|
||
|
|
||
|
/// Set the menu opening direction for the dropdown. Only useful when using
|
||
|
/// [ToolbarPosition.custom] since the toolbar otherwise automatically
|
||
|
/// determines the correct direction.
|
||
|
final DropdownMenuDirection? dropdownMenuDirection;
|
||
|
final double? dropdownMenuMaxHeight;
|
||
|
final BoxDecoration? dropdownBoxDecoration;
|
||
|
|
||
|
/// Styling options that only apply to the buttons:
|
||
|
/// (See the [ToggleButtons] class for more information)
|
||
|
|
||
|
final Color? buttonColor;
|
||
|
final Color? buttonSelectedColor;
|
||
|
final Color? buttonFillColor;
|
||
|
final Color? buttonFocusColor;
|
||
|
final Color? buttonHighlightColor;
|
||
|
final Color? buttonHoverColor;
|
||
|
final Color? buttonSplashColor;
|
||
|
final Color? buttonBorderColor;
|
||
|
final Color? buttonSelectedBorderColor;
|
||
|
final BorderRadius? buttonBorderRadius;
|
||
|
final double? buttonBorderWidth;
|
||
|
}
|
||
|
|
||
|
/// Other options such as the height of the widget and the decoration surrounding it
|
||
|
class OtherOptions {
|
||
|
const OtherOptions({
|
||
|
this.decoration = const BoxDecoration(
|
||
|
borderRadius: BorderRadius.all(Radius.circular(4)),
|
||
|
border:
|
||
|
Border.fromBorderSide(BorderSide(color: Color(0xffececec), width: 1)),
|
||
|
),
|
||
|
this.height = 400,
|
||
|
});
|
||
|
|
||
|
/// The BoxDecoration to use around the Html editor. By default, the widget
|
||
|
/// uses a thin, dark, rounded rectangle border around the widget.
|
||
|
final BoxDecoration decoration;
|
||
|
|
||
|
/// Sets the height of the Html editor widget. This takes the toolbar into
|
||
|
/// account (i.e. this sets the height of the entire widget rather than the
|
||
|
/// editor space)
|
||
|
///
|
||
|
/// The default value is 400.
|
||
|
final double height;
|
||
|
}
|