Skip to content

Commit 6d5631d

Browse files
Merge pull request #86 from owlistic-notes/inline-commands
Add inline "/task" command
2 parents 284e4b2 + dd47374 commit 6d5631d

File tree

2 files changed

+90
-1
lines changed

2 files changed

+90
-1
lines changed

src/frontend/lib/providers/note_editor_provider.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,9 @@ class NoteEditorProvider with ChangeNotifier implements NoteEditorViewModel {
521521

522522
// Track which block is being edited and schedule updates
523523
void _handleDocumentChange() {
524+
// TODO: improve change listener using a similar approach
525+
// to _findEditedTextNodes in markdown_inline_upstream_plugin.dart
526+
524527
// Get the node that's currently being edited
525528
final selection = _documentBuilder.composer.selection;
526529
if (selection == null) return;

src/frontend/lib/utils/document_builder.dart

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)