fluent_editor_review 1.0.0
fluent_editor_review: ^1.0.0 copied to clipboard
Review / Track Changes plugin for FluentEditor — allows switching between Editing and Suggesting modes with green additions, red strikethrough deletions, and text range anchoring.
fluent_editor_review #
Track Changes / Review plugin for Fluent Editor.
Provides a complete Track Changes workflow: captures text additions and deletions as reviewable suggestions, renders inline visual markers (green for additions, red strikethrough for deletions), and supports accept/reject actions — all through the core editor's plugin API with zero coupling to the editor internals.
Features #
- Editing / Review Mode Toggle: Toolbar button to switch between direct editing and review (track changes) mode
- Suggested Additions: New text is visually marked in green
- Suggested Deletions: Removed text is rendered with red strikethrough and preserved for review
- Accept / Reject: Reviewers can accept or reject individual suggestions
- Table Operations: Full track-changes support for table row/column insert, delete, and cell span changes
- List Operations: Track changes for list item addition, deletion, indent/outdent, and marker type changes
- Undo/Redo Integration: Full undo/redo support for all suggestion operations
- Unified Sidebar: Suggestion cards integrate seamlessly into the core editor's unified sidebar alongside comments
- Localization: All labels are customizable via
SuggestionLabels
Installation #
dependencies:
fluent_editor_review:
path: ../fluent-editor-review # or git URL
Quick Start #
import 'package:fluent_editor/fluent_editor.dart';
import 'package:fluent_editor_review/fluent_editor_review.dart';
// 1. Create the suggestion controller
final suggestionController = FluentSuggestionController();
// 2. Register the plugin with the editor
FluentEditor(
document: document,
plugins: [
FluentSuggestionPlugin(controller: suggestionController),
],
);
// The plugin adds an "Editing / Review" toggle button to the toolbar.
// When the user switches to Review mode, all edits are captured as suggestions.
Architecture #
┌──────────────────────────────────────────────────┐
│ fluent_editor │
│ │
│ FluentEditorPlugin (abstract) │
│ └─ onInsertCharacter() │
│ └─ onInsertText() / onBackspace() / onDelete()│
│ └─ onEnter() / onTab() │
│ └─ onInsertTableRow() / onDeleteTableRow() │
│ └─ onInsertTableColumn() / onDeleteTableColumn│
│ └─ onIncreaseTableRowspan() / ... │
│ └─ onColumnResize() / onRowResize() │
│ └─ buildSidebarItems() │
│ └─ RenderStyleHook (style rendering) │
└──────────────────────┬───────────────────────────┘
│ implements
┌──────────────────────▼───────────────────────────┐
│ fluent_editor_review │
│ │
│ FluentSuggestionPlugin │
│ └─ intercepts all edit operations │
│ └─ adds toolbar mode selector UI │
│ └─ builds FluentSidebarItem list for sidebar │
│ │
│ FluentSuggestionController │
│ └─ manages mode (editing / suggesting) │
│ └─ stores active suggestions │
│ └─ accept / reject logic │
│ └─ RenderStyleHook for visual markers │
│ │
│ Handlers │
│ └─ TextSuggestionHandler (text operations) │
│ └─ TableSuggestionHandler (table operations) │
│ │
│ Widgets │
│ └─ FluentSuggestionCard (sidebar card) │
│ └─ FluentSuggestionToolbarSelector (toolbar) │
│ └─ FluentSuggestionSidebar (sidebar wrapper) │
└──────────────────────────────────────────────────┘
Data Model #
FluentSuggestionMode #
| Value | Description |
|---|---|
editing |
Direct document editing (normal mode) |
suggesting |
Track changes mode — edits are captured as suggestions |
FluentSuggestionType #
| Value | Description |
|---|---|
addition |
Newly added text (rendered in green) |
deletion |
Text marked for removal (rendered with red strikethrough) |
FluentSuggestionStatus #
| Value | Description |
|---|---|
pending |
Active suggestion awaiting review |
accepted |
Suggestion accepted by reviewer |
rejected |
Suggestion rejected by reviewer |
Suggestion #
| Field | Type | Description |
|---|---|---|
id |
String |
Unique identifier (nanoid) |
nodeId |
String |
ID of the paragraph node the suggestion is anchored to |
startOffset |
int |
Start character offset within the paragraph |
endOffset |
int |
End character offset within the paragraph |
type |
FluentSuggestionType |
addition or deletion |
authorName |
String |
Author display name |
text |
String |
The added or deleted text content |
createdAt |
DateTime |
Creation timestamp |
status |
FluentSuggestionStatus |
pending, accepted, or rejected |
orphan |
bool |
Whether the anchored text has been removed by undo |
API Reference #
FluentSuggestionController #
The main controller managing suggestion state, mode switching, and accept/reject operations.
final controller = FluentSuggestionController();
// Set the current author name
controller.authorName = 'Alice';
// Switch between modes
controller.mode = FluentSuggestionMode.suggesting;
controller.mode = FluentSuggestionMode.editing;
// Toggle mode
controller.toggleMode();
// Check current mode
if (controller.isSuggestingMode) {
// In review mode — edits will be tracked
}
// Get all pending suggestions
final suggestions = controller.allSuggestions;
// Accept a suggestion (applies the change permanently)
controller.acceptSuggestion(document, suggestionId);
// Reject a suggestion (reverts the change)
controller.rejectSuggestion(document, suggestionId);
// Listen for changes
controller.addListener(() {
// suggestions changed, rebuild UI
});
// Dispose when done
controller.dispose();
FluentSuggestionPlugin #
Implements FluentEditorPlugin. Registered via FluentEditor(plugins: [...]).
FluentSuggestionPlugin(
controller: suggestionController,
labels: SuggestionLabels(
editingMode: 'Editing',
suggestingMode: 'Review',
additionLabel: 'Addition',
deletionLabel: 'Deletion',
acceptButton: 'Accept',
rejectButton: 'Reject',
),
);
Intercepted Operations
The plugin intercepts the following core editor operations when in Review mode:
| Hook | Description |
|---|---|
onInsertCharacter |
Captures typed characters as suggested additions |
onInsertText |
Captures pasted or IME text as suggested additions |
onImeCompositionCommit |
Captures IME composition commits |
onBackspace |
Marks text for deletion instead of removing it |
onDelete |
Marks text for deletion instead of removing it |
onDeleteNode |
Marks entire nodes (images, HR) for deletion |
onEnter |
Handles paragraph/list item splitting with suggestions |
onTab |
Handles list indent/outdent with suggestions |
onReplaceSelection |
Handles selection replacement with suggestions |
onInsertNode |
Captures node insertions (list items, HR, etc.) |
onInsertTableRow |
Captures table row additions |
onDeleteTableRow |
Captures table row removals |
onInsertTableColumn |
Captures table column additions |
onDeleteTableColumn |
Captures table column removals |
onIncreaseTableRowspan |
Captures cell merge (vertical) |
onDecreaseTableRowspan |
Captures cell split (vertical) |
onIncreaseTableColspan |
Captures cell merge (horizontal) |
onDecreaseTableColspan |
Captures cell split (horizontal) |
onColumnResize |
Captures column width changes |
onRowResize |
Captures row height changes |
Visual Rendering #
The plugin provides a RenderStyleHook that injects visual markers into the editor rendering pipeline:
- Additions: Green text color (
#2E7D32) with green underline - Deletions: Red text color (
#C62828) with red strikethrough
These styles are automatically applied to fragments that contain suggestion metadata — no manual styling is needed.
Sidebar Card #
Each suggestion appears as a FluentSuggestionCard in the unified sidebar:
- Author avatar: Circular avatar with author initial and deterministic color
- Timestamp: Relative time display (e.g., "2 hours ago")
- Type badge: "Addition" or "Deletion" label
- Preview text: Snippet of the added or deleted text
- Action buttons: Accept (✓ green) and Reject (✕ red) icon buttons with tooltips
Localization #
All labels are customizable via SuggestionLabels:
FluentSuggestionPlugin(
controller: controller,
labels: SuggestionLabels(
editingMode: 'Edición',
suggestingMode: 'Revisión',
additionLabel: 'Adición',
deletionLabel: 'Eliminación',
acceptButton: 'Aceptar',
rejectButton: 'Rechazar',
sidebarTitle: 'Actividades y Revisiones',
noSuggestions: 'No hay comentarios ni sugerencias.',
),
);
| Label | Default | Description |
|---|---|---|
editingMode |
'Editing' |
Toolbar toggle label for editing mode |
suggestingMode |
'Review' |
Toolbar toggle label for review mode |
sidebarTitle |
'Activities & Reviews' |
Sidebar header title |
noSuggestions |
'No comments or suggestions...' |
Empty sidebar message |
additionLabel |
'Addition' |
Badge label for addition suggestions |
deletionLabel |
'Deletion' |
Badge label for deletion suggestions |
acceptButton |
'Accept' |
Accept button tooltip |
rejectButton |
'Reject' |
Reject button tooltip |
acceptAllButton |
'Accept All' |
Accept all tooltip |
rejectAllButton |
'Reject All' |
Reject all tooltip |
imageLabel |
'[Image]' |
Placeholder text for image suggestions |
horizontalRuleLabel |
'[Horizontal Rule]' |
Placeholder text for HR suggestions |
Testing #
flutter analyze # static analysis
flutter test # unit tests (83 tests)
License #
MIT — see the LICENSE file.