fix: create document (#2701)

* fix: create a new document

* fix: the banner don't show after deleteing the page

* fix: inserting a divider through the slash menu the cursor should stay active in the next line

* fix: the overlay doesn't dismiss after selecting a page

* fix: typo

* fix: delete the page in document if it has been deleted

* chore: l10n

* chore: rename events

* ci: rm install_diesel in ci

* fix: cover color not working

* ci: fix tauri build

---------

Co-authored-by: nathan <nathan@appflowy.io>
This commit is contained in:
Lucas.Xu
2023-06-05 13:10:14 +08:00
committed by GitHub
parent 2bd90ba7ab
commit 80a273edae
37 changed files with 352 additions and 165 deletions

View File

@ -66,7 +66,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
await _subscribe(state);
emit(state);
},
deleted: (Deleted value) async {
moveToTrash: (MoveToTrash value) async {
emit(state.copyWith(isDeleted: true));
},
restore: (Restore value) async {
@ -74,11 +74,13 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
},
deletePermanently: (DeletePermanently value) async {
final result = await _trashService.deleteViews([view.id]);
emit(state.copyWith(forceClose: result.swap().isLeft()));
final forceClose = result.fold((l) => true, (r) => false);
emit(state.copyWith(forceClose: forceClose));
},
restorePage: (RestorePage value) async {
final result = await _trashService.putback(view.id);
emit(state.copyWith(isDeleted: result.swap().isRight()));
final isDeleted = result.fold((l) => false, (r) => true);
emit(state.copyWith(isDeleted: isDeleted));
},
);
}
@ -98,8 +100,12 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
/// subscribe to the view(document page) change
void _onViewChanged() {
_viewListener.start(
onViewDeleted: (r) =>
r.swap().map((r) => add(const DocumentEvent.deleted())),
onViewMoveToTrash: (r) {
r.swap().map((r) => add(const DocumentEvent.moveToTrash()));
},
onViewDeleted: (r) {
r.swap().map((r) => add(const DocumentEvent.moveToTrash()));
},
onViewRestored: (r) =>
r.swap().map((r) => add(const DocumentEvent.restore())),
);
@ -161,7 +167,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
@freezed
class DocumentEvent with _$DocumentEvent {
const factory DocumentEvent.initial() = Initial;
const factory DocumentEvent.deleted() = Deleted;
const factory DocumentEvent.moveToTrash() = MoveToTrash;
const factory DocumentEvent.restore() = Restore;
const factory DocumentEvent.restorePage() = RestorePage;
const factory DocumentEvent.deletePermanently() = DeletePermanently;

View File

@ -88,35 +88,22 @@ extension DocumentDataPBFromTo on DocumentDataPB {
}
}
class _BackendKeys {
const _BackendKeys._();
static const String text = 'text';
}
extension BlockToNode on BlockPB {
Node toNode({
Iterable<Node>? children,
}) {
return Node(
id: id,
type: _typeAdapter(ty),
type: ty,
attributes: _dataAdapter(ty, data),
children: children ?? [],
);
}
String _typeAdapter(String ty) {
final adapter = {
_BackendKeys.text: ParagraphBlockKeys.type,
};
return adapter[ty] ?? ty;
}
Attributes _dataAdapter(String ty, String data) {
final map = Attributes.from(jsonDecode(data));
final adapter = {
'text': (Attributes map) => map
ParagraphBlockKeys.type: (Attributes map) => map
..putIfAbsent(
'delta',
() => Delta().toJson(),
@ -134,7 +121,7 @@ extension NodeToBlock on Node {
assert(id.isNotEmpty);
final block = BlockPB.create()
..id = id
..ty = _typeAdapter(type)
..ty = type
..data = _dataAdapter(type, attributes);
if (childrenId != null && childrenId.isNotEmpty) {
block.childrenId = childrenId;
@ -145,13 +132,6 @@ extension NodeToBlock on Node {
return block;
}
String _typeAdapter(String type) {
final adapter = {
'paragraph': 'text',
};
return adapter[type] ?? type;
}
String _dataAdapter(String type, Attributes attributes) {
return jsonEncode(attributes);
}

View File

@ -112,6 +112,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: double.infinity,
maxHeight: double.infinity,
),
child: FloatingToolbar(
style: styleCustomizer.floatingToolbarStyleBuilder(),
@ -225,7 +226,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
];
final colorAction = [
// OptionAction.divider,
OptionAction.divider,
OptionAction.color,
];

View File

@ -1,9 +1,11 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/select_option_editor.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart';
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide FlowySvg;
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flutter/material.dart';
import 'package:styled_widget/styled_widget.dart';
@ -45,7 +47,7 @@ class ColorOptionAction extends PopoverActionCell {
@override
String get name {
return 'Color'; // todo: l10n
return LocaleKeys.toolbar_color.tr();
}
@override
@ -55,7 +57,6 @@ class ColorOptionAction extends PopoverActionCell {
if (selection == null) {
return const SizedBox.shrink();
}
// TODO: should we support multiple selection?
final node = editorState.getNodeAtPath(selection.start.path);
if (node == null) {
return const SizedBox.shrink();
@ -70,6 +71,8 @@ class ColorOptionAction extends PopoverActionCell {
return SelectOptionColorList(
selectedColor: selectedColor,
onSelectedColor: (color) {
controller.close();
final nodes = editorState.getNodesInSelection(selection);
final transaction = editorState.transaction;
for (final node in nodes) {
@ -79,8 +82,6 @@ class ColorOptionAction extends PopoverActionCell {
});
}
editorState.apply(transaction);
controller.close();
},
);
};
@ -141,22 +142,22 @@ class OptionActionWrapper extends ActionCell {
switch (inner) {
// TODO: l10n
case OptionAction.delete:
description = 'Delete';
description = LocaleKeys.document_plugins_optionAction_delete.tr();
break;
case OptionAction.duplicate:
description = 'Duplicate';
description = LocaleKeys.document_plugins_optionAction_duplicate.tr();
break;
case OptionAction.turnInto:
description = 'Turn into';
description = LocaleKeys.document_plugins_optionAction_turnInto.tr();
break;
case OptionAction.moveUp:
description = 'Move up';
description = LocaleKeys.document_plugins_optionAction_moveUp.tr();
break;
case OptionAction.moveDown:
description = 'Move down';
description = LocaleKeys.document_plugins_optionAction_moveDown.tr();
break;
case OptionAction.color:
description = 'Color';
description = LocaleKeys.document_plugins_optionAction_color.tr();
break;
case OptionAction.divider:
throw UnimplementedError();

View File

@ -69,6 +69,10 @@ class _BuiltInPageWidgetState extends State<BuiltInPageWidget> {
return _build(context, page);
}
if (snapshot.connectionState == ConnectionState.done) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
// just delete the page if it is not found
_deletePage();
});
return const Center(
child: FlowyText('Cannot load the page'),
);
@ -174,6 +178,12 @@ class _BuiltInPageWidgetState extends State<BuiltInPageWidget> {
),
);
}
Future<void> _deletePage() async {
final transaction = widget.editorState.transaction;
transaction.deleteNode(widget.node);
widget.editorState.apply(transaction);
}
}
enum _ActionType {

View File

@ -23,7 +23,8 @@ void showLinkToPageMenu(
final top = alignment == Alignment.bottomLeft ? offset.dy : null;
final bottom = alignment == Alignment.topLeft ? offset.dy : null;
final linkToPageMenuEntry = FullScreenOverlayEntry(
late OverlayEntry linkToPageMenuEntry;
linkToPageMenuEntry = FullScreenOverlayEntry(
top: top,
bottom: bottom,
left: offset.dx,
@ -35,6 +36,7 @@ void showLinkToPageMenu(
hintText: pageType.toHintText(),
onSelected: (appPB, viewPB) {
editorState.insertPage(appPB, viewPB);
linkToPageMenuEntry.remove();
},
),
),

View File

@ -465,17 +465,11 @@ class _CoverColorPickerState extends State<CoverColorPicker> {
},
platform: TargetPlatform.windows,
),
child: ListView.builder(
controller: scrollController,
shrinkWrap: true,
itemCount: widget.backgroundColorOptions.length,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return _buildColorItems(
widget.backgroundColorOptions,
widget.selectedBackgroundColorHex,
);
},
child: SingleChildScrollView(
child: _buildColorItems(
widget.backgroundColorOptions,
widget.selectedBackgroundColorHex,
),
),
),
);

View File

@ -303,10 +303,11 @@ class _CoverImageState extends State<_CoverImage> {
CoverSelectionType get selectionType => CoverSelectionType.fromString(
widget.node.attributes[CoverBlockKeys.selectionType],
);
Color get color => Color(
int.tryParse(widget.node.attributes[CoverBlockKeys.selection]) ??
0xFFFFFFFF,
);
Color get color {
final hex = widget.node.attributes[CoverBlockKeys.selection] as String?;
return hex?.toColor() ?? Colors.white;
}
bool get hasIcon =>
widget.node.attributes[CoverBlockKeys.iconSelection] == null
? false

View File

@ -35,7 +35,7 @@ CharacterShortcutEventHandler _convertMinusesToDividerHandler =
..insertNode(path, dividerNode())
..insertNode(path, paragraphNode())
..deleteNode(node)
..afterSelection = Selection.collapse(path.next, 0);
..afterSelection = Selection.collapse(path, 0);
editorState.apply(transaction);
return true;
};
@ -61,7 +61,9 @@ SelectionMenuItem dividerMenuItem = SelectionMenuItem(
}
final insertedPath = delta.isEmpty ? path : path.next;
final transaction = editorState.transaction
..insertNode(insertedPath, dividerNode());
..insertNode(insertedPath, dividerNode())
..insertNode(insertedPath, paragraphNode())
..afterSelection = Selection.collapse(insertedPath.next, 0);
editorState.apply(transaction);
},
);