mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
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:
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
];
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
},
|
||||
);
|
||||
|
Reference in New Issue
Block a user