✨ rich_editor
WYSIWYG editor for Flutter with a rich set of supported formatting options.
Based on https://github.com/dankito/RichTextEditor, but for Flutter.
Supports Android and iOS. The editor is Tiptap (ProseMirror) running in a WebView (flutter_inappwebview). It needs iOS 14.5 or later, and Android System WebView 80 or later.
✨ Features
xBold, Italic, Underline, Strike through, Subscript, SuperscriptxHeading 1 - 6, Text body, Preformatted, Block quotexFont (reads all system fonts) (Android only)xFont SizexText ColorxText Background ColorxJustify Left, Center, Right, FullxIndent, OutdentxUndo, RedoxUnordered List (Bullets)xOrdered List (Numbers)xInsert local or remote Image, or paste onexInsert video (YouTube links or video files)xInsert LinkxInsert CheckboxxToolbar highlights active formattingxChange notifications (onChanged) and a controllerxCustomizable and localizable toolbarxRead-only modexEdit and remove linksxPlain text output (getText)xJSON output (getJson/setJson)xFollows the app's light and dark themexRight-to-left languages (follows the app's text direction)xPasted HTML is sanitized, and a Content-Security-Policy blocks scripts in the contentSearch
📸 Screenshots
Usage
final controller = RichEditorController();
// Insert widget into tree
RichEditor(
controller: controller,
value: '<p>initial html here</p>',
editorOptions: const RichEditorOptions(
placeholder: 'Start typing',
// backgroundColor: Colors.blueGrey, // Editor's bg color
// baseTextColor: Colors.white,
// editor padding
padding: EdgeInsets.symmetric(horizontal: 5.0),
// font name
baseFontFamily: 'sans-serif',
// Position of the editing bar (BarPosition.top, .bottom or .custom)
barPosition: BarPosition.top,
),
// Called with the new HTML whenever the user edits
onChanged: (html) => print(html),
// Return a link for a picked, pasted or dropped image: upload it to your
// storage first, or use a data URI.
getImageUrl: (image) async {
final base64 = base64Encode(await image.readAsBytes());
return 'data:image/png;base64,$base64';
},
onUploadError: (error, stackTrace) => print('Upload failed: $error'),
)
Dispose the controller when you're done with it.
Set readOnly: true to show the content without the toolbar and without
letting the user edit it, and autofocus: true to focus the editor once it has
loaded. Android only opens the keyboard from code once the user has interacted
with the editor; before that, focus() places the caret.
Without getImageUrl, picked and pasted images are embedded in the HTML as
data: URLs. Picking videos from the gallery needs getVideoUrl.
Get or set the HTML, or get the plain text
String html = await controller.getHtml(); // empty when the editor is empty
await controller.setHtml('<p>Hello</p>');
String text = await controller.getText(); // paragraphs on their own lines
The HTML is the editor's own: whatever you set is parsed into the editor's
document, so markup it has no formatting for is dropped and the rest comes
back in one consistent form (e.g. <b> comes back as <strong>, <font> as a
styled <span>).
Or store the content as JSON, the Tiptap / ProseMirror document, which other Tiptap and ProseMirror editors and renderers read as well:
Map<String, dynamic> json = await controller.getJson();
bool ok = await controller.setJson(json); // false if it doesn't fit the editor
Set Focus and Unfocus
await controller.focus();
await controller.unFocus();
Clear Editor content
await controller.clear();
Run formatting commands
await controller.executor.setBold();
await controller.executor.insertLink('https://flutter.dev', 'Flutter');
GlobalKey<RichEditorState> still works too:
keyEditor.currentState?.getHtml().
Customize the toolbar
Choose and order the buttons, and translate the texts:
RichEditorOptions(
toolbarItems: const [
ToolbarItem.bold,
ToolbarItem.italic,
ToolbarItem.link,
ToolbarItem.bulletList,
],
labels: const RichEditorLabels(bold: 'Fett', italic: 'Kursiv'),
)
Custom Toolbar
Set barPosition: BarPosition.custom and build your own toolbar from the
controller. It is a ValueListenable<RichEditorValue>, so buttons can show
the formatting at the cursor:
ValueListenableBuilder<RichEditorValue>(
valueListenable: controller,
builder: (context, value, _) => TabButton(
icon: Icons.format_bold,
selected: value.isActive(EditorCommand.bold),
onTap: controller.executor.setBold,
),
)
See custom_toolbar_demo.dart in the example.
Platform setup
Follow the setup of flutter_inappwebview
and image_picker (for iOS, the photo
library usage description in Info.plist).
On iOS the editor is served from http://localhost inside your app. If
App Transport Security blocks it, set NSAllowsLocalNetworking to true
under NSAppTransportSecurity in Info.plist.
Development
flutter test # the Dart package
(cd test/js && npm ci && npm test) # the editor JavaScript, in jsdom
The editor page runs assets/editor/editor.js, which is bundled from
tool/editor/src. After changing the sources, rebuild it and commit both:
(cd tool/editor && npm ci && npm run build)
CI runs the tests, checks the bundle matches its sources, and runs
dart format and flutter analyze, on every pull request.
License
Copyright 2021 JideGuru
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.