novident_editor 1.0.4
novident_editor: ^1.0.4 copied to clipboard
A highly customizable rich-text editor for Flutter, part of the Novident suite. Fork of AppFlowy Editor (AppFlowy-IO), used under the Mozilla Public License 2.0.
Novident Editor
A high-performance rich-text editor for Flutter — part of the Novident suite.
Fork of AppFlowy Editor, used under the Mozilla Public License 2.0. See NOTICE for attribution.
Novident Editor is a drop-in rich-text editor for Flutter apps. It renders a document tree built from composable block components — paragraphs, headings, lists, quotes, images, tables and more.
Unique Features #
- Vim emulation (normal / insert / visual, remappable keybindings, pending operator
dd) - Zen mode (typewriter centering, unfocused-block dimming, color neutralization without touching the document)
- Aggressive caching throughout the document model, selection pipeline and text rendering
- Named paragraph styles with
basedOninheritance chains, similar to Word — font family, font size, bold, italic, spacing, colours and more, all resolved through a three-tier fallback (explicit style → type default → global default) and exposed through customisable toolbar dropdowns - First-line indent per style (
NovidentStyleIndent.firstLineIndent) or globally (EditorStyle.firstLineIndent) - Full control over tables using NovidentTableStyleDefinition and new configurations for tables.
- Extracted important layers of the editor API like the rich text component, the selection components, the core elements of the editor, and the document format that avoid using the whole editor for small things
Note
Planned:
- Improve perfomance on Zen Mode.
- Full customization of every default block.
- Improve clipboard copy+paste content (now it copies pure plain text)
- Translations
- Typewriting Scrolling without Zen Mode.
- Uncouple important parts of the editor into individual packages (block components, keyboard service, scroll service, etc)
If you were using version 1.0.4 #
Quick start #
import 'package:novident_editor/novident_editor.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: const [
NovidentEditorLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
home: Scaffold(
body: NovidentEditor(
editorState: EditorState.blank(withInitialText: true),
),
),
);
}
}
Or hydrate the editor from JSON / Markdown / Quill Delta — see Importing content.
Vim mode #
Vim emulation is built into the package — no extra dependency. Every keybinding is remappable at runtime, and changing bindings does not rebuild the editor:
final vimController = VimModeController();
NovidentEditor(
editorState: editorState,
editorStyle: EditorStyle.desktop(
// The vim renderer paints a block cursor in normal/visual mode.
// Insert mode keeps the standard thin caret.
selectionRenderer: VimSelectionRenderer(
controller: vimController,
),
),
commandShortcutEvents: [
...vimController.commandShortcutEvents,
...standardCommandShortcutEvents,
],
);
// After the editor is mounted:
vimController.attach(editorState);
// Remap any key at runtime:
vimController.configuration =
vimController.configuration.rebind(VimCommand.moveLeft, 'a', rawCommand: null);
Customising the block cursor #
The block cursor color defaults to EditorStyle.cursorColor. Configure width,
blink, and opacity through VimCursorStyle:
final vimController = VimModeController(
configuration: VimModeConfiguration(
cursorStyle: VimCursorStyle(
color: Colors.purple,
opacity: 0.55,
blink: false,
),
),
);
Change it at runtime without rebuilding:
vimController.configuration = vimController.configuration.copyWith(
cursorStyle: const VimCursorStyle(blockWidth: 20, blink: true),
);
A VimModeChip widget is available in the example for status bars. Modes, the pending dd operator
and the block-cursor style are all observable via vimController.
You can also define your own VimCommand instances to extend the built-in set —
indent / outdent, custom insertions, or any editor operation. See the full guide
at Vim Commands.
Zen mode #
Zen mode dims every top-level block that is not focused (animatable opacity), ignores text / highlight / block-background colors (they stay in the document — disable zen and they come back), and keeps the focused block vertically centered (typewriter scrolling):
final zenController = ZenModeController();
NovidentEditor(
editorState: editorState,
editorScrollController: editorScrollController,
blockWrapper: zenController.blockWrapper,
editorStyle: EditorStyle.desktop(
textSpanDecorator: zenController.textSpanDecorator(),
),
);
All colours (font_color, bg_color, block bgColor) are ignored without ever
mutating the delta or the node attributes — the visual pipeline neutralises them
while the blockWrapper dims the unfocused blocks. This means zen mode can be toggled
on and off with zero document churn.
Word & character counter #
Each EditorState can be connected to a WordCountService that debounces on
transactions and exposes documentCounters / selectionCounters through a
ChangeNotifier. Use a ListenableBuilder to build a live counter chip:
final counter = WordCountService(editorState: editorState)..register();
ListenableBuilder(
listenable: counter,
builder: (context, _) => Text(
'${counter.documentCounters.wordCount} words '
'${counter.documentCounters.charCount} chars',
),
);
Styles #
Novident Editor includes a named paragraph style system with basedOn
inheritance — define a base style once and every derived style inherits its
font, size, spacing and colours automatically:
Important
Base styles require to have defined: fontSize, fontFamily and textColor properties always
NovidentEditor(
editorState: editorState,
styles: NovidentStylesConfig(
registry: NovidentStyleRegistry({
'base': NovidentStyleDefinition(
id: 'base',
name: 'Base',
fontSize: 12,
fontFamily: 'Arial',
indent: NovidentStyleIndent.defaultLineFilter(),
),
'body': NovidentStyleDefinition.nextSame(
id: 'body',
name: 'Body',
basedOn: 'base',
spacing: NovidentStyleSpacing(after: 8),
),
'heading-1': NovidentStyleDefinition(
id: 'heading-1',
name: 'Heading 1',
basedOn: 'base',
fontSize: 32,
bold: true,
spacing: NovidentStyleSpacing(before: 24, after: 12),
next: 'body',
),
}),
defaultStyle: /* base */,
defaultStylesByType: {'paragraph': /* body */, 'heading': /* heading-1 */},
),
);
Three toolbar items ship out of the box:
| Item | What it does |
|---|---|
styleToolbarItem |
Dropdown — applies a named style to the current block |
buildFontFamilyItem(fontFamilies: [...]) |
Dropdown — applies inline font family |
buildFontSizeItem(minSize: 1, maxSize: 99) |
Scrollable dropdown — applies inline font size |
All three resolve the current value by checking inline delta attributes
first, then the resolved node style (through the basedOn chain), and
finally a hard default. Colours and tooltips are fully customisable via the
standard toolbar theming parameters.
Font families are managed through NovidentFontProvider — inject your own
list (or use system_fonts on desktop) and the editor guarantees every
style resolves to a non-null font:
NovidentEditor(
fontProvider: NovidentFontProvider.fromList(
['Arial', 'Times New Roman', 'Georgia'],
defaultFamily: 'Arial',
),
);
See Styles — full guide for every property, the resolution algorithm, the font provider, and how to build your own style registry.
Customising block components #
Overriding a built-in block or adding a new one is done via
blockComponentBuilders — the standardBlockComponentBuilderMap is a
convenient base that you can spread and override:
NovidentEditor(
editorState: editorState,
blockComponentBuilders: {
...standardBlockComponentBuilderMap,
'my_custom_type': MyCustomBlockBuilder(),
},
);
See documentation/customizing.md for a
detailed walkthrough.
Tables #
Tables render with a weight-based column layout that fills available width.
Define reusable table styles with NovidentTableStyleDefinition for zebra
striping, coloured headers, borderless layouts, and more.
final table = TableNode.fromList([
['Name', 'Elara'], // column 0
['Role', 'Mage'], // column 1
]);
editorState.insertNode(path, table.node);
See Tables — full guide for creation, styling, column weights, keyboard shortcuts, and the complete property reference.
License #
Novident Editor is a fork of AppFlowy Editor (AppFlowy-IO/appflowy-editor). Upstream is dual-licensed under the GNU Affero General Public License v3 and the Mozilla Public License 2.0.
This fork is used and distributed under the Mozilla Public License 2.0. See LICENSE and NOTICE for full details.