@@ -22,9 +22,13 @@ class DocumentBuilder {
2222 late Editor _editor;
2323 late FocusNode _editorFocusNode;
2424
25+ // Toolbar components
2526 final _floatingToolbarOverlayController = OverlayPortalController ();
2627 final _selectionLayerLinks = SelectionLayerLinks ();
2728
29+ // Plugins
30+ late final ActionTagsPlugin _inlineCommandsPlugin;
31+
2832 // Add instance of AttributedTextUtils
2933 static final AttributedTextUtils _attributedTextUtils = AttributedTextUtils ();
3034
@@ -95,6 +99,12 @@ class DocumentBuilder {
9599 _editor =
96100 createDefaultDocumentEditor (document: _document, composer: _composer);
97101
102+ // Add action tags listener for inline commands
103+ _inlineCommandsPlugin = ActionTagsPlugin ();
104+ _inlineCommandsPlugin.attach (_editor);
105+ _inlineCommandsPlugin.composingActionTag.addListener (_handleInlineCommand);
106+
107+
98108 // Create focus node
99109 _editorFocusNode = FocusNode ();
100110
@@ -104,11 +114,82 @@ class DocumentBuilder {
104114 }
105115
106116 void dispose () {
117+ _inlineCommandsPlugin.composingActionTag.removeListener (_handleInlineCommand);
118+ _inlineCommandsPlugin.detach (_editor);
107119 _editorFocusNode.dispose ();
108120 _composer.dispose ();
109121 _editor.dispose ();
110122 }
111123
124+ void _handleInlineCommand () {
125+ for (final node in _document) {
126+ if (node is ! TextNode ) {
127+ continue ;
128+ }
129+
130+ final actionSpans = node.text.getAttributionSpansInRange (
131+ attributionFilter: (a) => a == actionTagComposingAttribution,
132+ range: SpanRange (0 , node.text.length - 1 ),
133+ );
134+
135+ for (final actionSpan in actionSpans) {
136+ final action = node.text.substring (actionSpan.start + 1 , actionSpan.end + 1 );
137+ final actionText = node.text.substring (actionSpan.end + 1 );
138+ switch (action) {
139+ case 'task' :
140+ final editedNodeId = _composer.selection! .extent.nodeId;
141+ final newCaretPosition = DocumentPosition (
142+ nodeId: editedNodeId,
143+ nodePosition: TextNodePosition (offset: actionSpan.start),
144+ );
145+ _editor.execute ([
146+ ConvertParagraphToTaskRequest (
147+ nodeId: _composer.selection! .extent.nodeId,
148+ isComplete: false ,
149+ ),
150+ // Delete the whole block content.
151+ DeleteContentRequest (
152+ documentRange: DocumentRange (
153+ start: DocumentPosition (
154+ nodeId: editedNodeId,
155+ nodePosition: TextNodePosition (offset: actionSpan.start),
156+ ),
157+ end: DocumentPosition (
158+ nodeId: editedNodeId,
159+ nodePosition: TextNodePosition (offset: actionSpan.end + 1 ),
160+ ),
161+ ),
162+ ),
163+ // Insert the content without the /command
164+ InsertAttributedTextRequest (
165+ DocumentPosition (
166+ nodeId: editedNodeId,
167+ nodePosition: const TextNodePosition (offset: 0 ),
168+ ),
169+ AttributedText (actionText),
170+ ),
171+ // Adjust the caret position to reflect any Markdown syntax characters that
172+ // were removed.
173+ ChangeSelectionRequest (
174+ DocumentSelection .collapsed (
175+ position: newCaretPosition,
176+ ),
177+ SelectionChangeType .alteredContent,
178+ SelectionReason .contentChange,
179+ ),
180+ ChangeComposingRegionRequest (
181+ DocumentRange (
182+ start: newCaretPosition,
183+ end: newCaretPosition,
184+ ),
185+ ),
186+ ]);
187+ break ;
188+ }
189+ }
190+ }
191+ }
192+
112193 // Add document structure change listener to detect new/deleted nodes
113194 void addDocumentStructureListener (void Function (dynamic ) listener) {
114195 _document.addListener (listener);
@@ -1278,7 +1359,12 @@ class DocumentBuilder {
12781359 ),
12791360 ],
12801361 plugins: {
1281- MarkdownInlineUpstreamSyntaxPlugin (),
1362+ // Inline Markdown
1363+ MarkdownInlineUpstreamSyntaxPlugin (
1364+ parsers: const [ StyleUpstreamMarkdownSyntaxParser () ]
1365+ ),
1366+ // Tasks
1367+ _actionTagPlugin,
12821368 }),
12831369 )
12841370 );
0 commit comments