Skip to content

Commit 11d05b3

Browse files
authored
feat: support inserting local image (#2913)
1 parent c870dbe commit 11d05b3

File tree

12 files changed

+484
-27
lines changed

12 files changed

+484
-27
lines changed

frontend/appflowy_flutter/assets/translations/en.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,12 @@
452452
"center": "Center",
453453
"right": "Right",
454454
"defaultColor": "Default"
455+
},
456+
"image": {
457+
"copiedToPasteBoard": "The image link has been copied to the clipboard"
458+
},
459+
"outline": {
460+
"addHeadingToCreateOutline": "Add headings to create a table of contents."
455461
}
456462
}
457463
},

frontend/appflowy_flutter/integration_test/document/edit_document_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ const _sample = r'''
118118
---
119119
[] Highlight any text, and use the editing menu to _style_ **your** writing `however` you ~~like.~~
120120
121-
[] Type / followed by /bullet or /num to create a list.
121+
[] Type followed by bullet or num to create a list.
122122
123123
[x] Click `+ New Page` button at the bottom of your sidebar to add a new page.
124124

frontend/appflowy_flutter/lib/plugins/document/application/doc_bloc.dart

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import 'package:appflowy/plugins/document/application/doc_service.dart';
99
import 'package:appflowy_backend/protobuf/flowy-document2/protobuf.dart';
1010
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pbserver.dart';
1111
import 'package:appflowy_editor/appflowy_editor.dart'
12-
show EditorState, LogLevel, TransactionTime;
12+
show EditorState, LogLevel, TransactionTime, Selection, paragraphNode;
1313
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
1414
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
1515
import 'package:flutter/foundation.dart';
@@ -155,6 +155,9 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
155155
return;
156156
}
157157
await _transactionAdapter.apply(event.$2, editorState);
158+
159+
// check if the document is empty.
160+
applyRules();
158161
});
159162

160163
// output the log from the editor when debug mode
@@ -166,6 +169,39 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
166169
};
167170
}
168171
}
172+
173+
Future<void> applyRules() async {
174+
ensureAtLeastOneParagraphExists();
175+
ensureLastNodeIsEditable();
176+
}
177+
178+
Future<void> ensureLastNodeIsEditable() async {
179+
final editorState = this.editorState;
180+
if (editorState == null) {
181+
return;
182+
}
183+
final document = editorState.document;
184+
final lastNode = document.root.children.lastOrNull;
185+
if (lastNode == null || lastNode.delta == null) {
186+
final transaction = editorState.transaction;
187+
transaction.insertNode([document.root.children.length], paragraphNode());
188+
await editorState.apply(transaction);
189+
}
190+
}
191+
192+
Future<void> ensureAtLeastOneParagraphExists() async {
193+
final editorState = this.editorState;
194+
if (editorState == null) {
195+
return;
196+
}
197+
final document = editorState.document;
198+
if (document.root.children.isEmpty) {
199+
final transaction = editorState.transaction;
200+
transaction.insertNode([0], paragraphNode());
201+
transaction.afterSelection = Selection.collapse([0], 0);
202+
await editorState.apply(transaction);
203+
}
204+
}
169205
}
170206

171207
@freezed

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
2-
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/option_action.dart';
3-
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_list.dart';
42
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
53
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
64
import 'package:appflowy/plugins/document/presentation/editor_plugins/inline_page/inline_page_reference.dart';
75
import 'package:appflowy_editor/appflowy_editor.dart';
6+
import 'package:collection/collection.dart';
87
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
98
import 'package:flutter/material.dart';
109
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -55,20 +54,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
5554
highlightColorItem,
5655
];
5756

58-
late final slashMenuItems = [
59-
inlineGridMenuItem(documentBloc),
60-
referencedGridMenuItem,
61-
inlineBoardMenuItem(documentBloc),
62-
referencedBoardMenuItem,
63-
inlineCalendarMenuItem(documentBloc),
64-
referencedCalendarMenuItem,
65-
calloutItem,
66-
mathEquationItem,
67-
codeBlockItem,
68-
emojiMenuItem,
69-
autoGeneratorMenuItem,
70-
outlineItem,
71-
];
57+
late final List<SelectionMenuItem> slashMenuItems;
7258

7359
late final Map<String, BlockComponentBuilder> blockComponentBuilders =
7460
_customAppFlowyBlockComponentBuilders();
@@ -119,6 +105,9 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
119105
@override
120106
void initState() {
121107
super.initState();
108+
109+
slashMenuItems = _customSlashMenuItems();
110+
122111
effectiveScrollController = widget.scrollController ?? ScrollController();
123112
}
124113

@@ -219,6 +208,15 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
219208
),
220209
ImageBlockKeys.type: ImageBlockComponentBuilder(
221210
configuration: configuration,
211+
showMenu: true,
212+
menuBuilder: (node, state) => Positioned(
213+
top: 0,
214+
right: 10,
215+
child: ImageMenu(
216+
node: node,
217+
state: state,
218+
),
219+
),
222220
),
223221
DatabaseBlockKeys.gridType: DatabaseViewBlockComponentBuilder(
224222
configuration: configuration,
@@ -254,8 +252,15 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
254252
),
255253
AutoCompletionBlockKeys.type: AutoCompletionBlockComponentBuilder(),
256254
SmartEditBlockKeys.type: SmartEditBlockComponentBuilder(),
257-
ToggleListBlockKeys.type: ToggleListBlockComponentBuilder(),
258-
OutlineBlockKeys.type: OutlineBlockComponentBuilder(),
255+
ToggleListBlockKeys.type: ToggleListBlockComponentBuilder(
256+
configuration: configuration,
257+
),
258+
OutlineBlockKeys.type: OutlineBlockComponentBuilder(
259+
configuration: configuration.copyWith(
260+
placeholderTextStyle: (_) =>
261+
styleCustomizer.outlineBlockPlaceholderStyleBuilder(),
262+
),
263+
),
259264
};
260265

261266
final builders = {
@@ -325,6 +330,34 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
325330
return builders;
326331
}
327332

333+
List<SelectionMenuItem> _customSlashMenuItems() {
334+
final items = [...standardSelectionMenuItems];
335+
final imageItem = items.firstWhereOrNull(
336+
(element) => element.name == AppFlowyEditorLocalizations.current.image,
337+
);
338+
if (imageItem != null) {
339+
final imageItemIndex = items.indexOf(imageItem);
340+
if (imageItemIndex != -1) {
341+
items[imageItemIndex] = customImageMenuItem;
342+
}
343+
}
344+
return [
345+
...items,
346+
inlineGridMenuItem(documentBloc),
347+
referencedGridMenuItem,
348+
inlineBoardMenuItem(documentBloc),
349+
referencedBoardMenuItem,
350+
inlineCalendarMenuItem(documentBloc),
351+
referencedCalendarMenuItem,
352+
calloutItem,
353+
outlineItem,
354+
mathEquationItem,
355+
codeBlockItem,
356+
emojiMenuItem,
357+
autoGeneratorMenuItem,
358+
];
359+
}
360+
328361
(bool, Selection?) _computeAutoFocusParameters() {
329362
if (widget.editorState.document.isEmpty) {
330363
return (true, Selection.collapse([0], 0));

0 commit comments

Comments
 (0)