Compare commits

...
12 Commits
Author SHA1 Message Date
Lucas e527a1843e chore: bump version 0.7.3 (#6743)
* chore: update changelog

* feat: add 'custom namespace' in plan description
2024-11-08 10:13:17 +08:00
Richard Shiue 3f8eb70ff3 chore: bump collab (#6746) 2024-11-08 02:33:28 +08:00
Richard ShiueandMathias Mogensen bd7976d005 fix: v0.7.3 additional launch review session issues (#6730)
* fix: focusedDay on is range toggle

* fix: attempt to fix date reminder

* chore: alignment issue on windows

* chore: use onFailure

* chore: default for set reminder

* chore: code style

* fix: add reminder bug

* fix: checklist item alignment

* fix: date cell reminders

* chore: regard cell datetime as correct

* fix: reminder creation date

* chore: bump collab that fixes reminders

* test: add date cell and reminder bloc test

* chore: bump collab

* chore: revert visual density change

* fix: submitting date time text field makes it flash

* fix: improve behavior of phantom checklist item

---------

Co-authored-by: Mathias Mogensen <mathias@appflowy.io>
2024-11-07 18:33:12 +08:00
Lucas 4e1532af3e fix: application window is not visible on Windows (#6740)
* fix: try to fix the windows not visible issue

* chore: update win32_window

* chore: adjust the new api
2024-11-07 15:55:38 +08:00
Mathias Mogensen 97999aee44 fix: block option interceptor (#6739)
* fix: use unique key in interceptor

* test: add block option interaction tests
2024-11-07 09:47:47 +08:00
Nathan.fooo 6785104c3a chore: send notification for file upload state (#6738)
* chore: send notification for file upload state

* chore: fix test
2024-11-07 00:00:54 +08:00
Mathias Mogensen 193c8242e2 fix: mobile media cleanup (#6733) 2024-11-06 08:03:20 +01:00
Kilu.He 59eda5e038 fix: modified invitation page text (#6734) 2024-11-06 13:36:27 +08:00
Nathan.fooo 3c8de8a52c chore: update upload file logs (#6735) 2024-11-06 11:18:40 +08:00
Zack d38a5f38e3 chore: update dep (#6727) 2024-11-06 09:12:16 +08:00
Lucas fe4f85a597 fix: align the namespace and toggle shortcuts (#6726)
* chore: update translations, url to URL

* fix: children disapper when using toggle shortcuts

* fix: add tooltip for namespace button

* test: add toggle shortcut test
2024-11-06 09:08:59 +08:00
Mathias Mogensen 0a1af4e61f fix: 0.7.3 improvements (#6731)
* fix: recheck workspace usage on workspace changed

* fix: rename insert page item
2024-11-06 00:50:27 +01:00
49 changed files with 1287 additions and 860 deletions

No files matched your search

+14
View File
@@ -1,4 +1,18 @@
# Release Notes
## Version 0.7.3 - 07/11/2024
### New Features
- Enable custom URLs for published pages
- Support toggling headings
- Create a subpage by typing in the document
- Turn selected blocks into a subpage
- Add a manual date picker for the Date property
### Bug Fixes
- Fixed an issue where the workspace owner was unable to delete spaces created by others
- Fixed cursor height inconsistencies with text height
- Fixed editing issues in Kanban cards
- Fixed an issue preventing images or files from being dropped into empty paragraphs
## Version 0.7.2 - 22/10/2024
### New Features
- Copy link to block
@@ -0,0 +1,47 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Block option interaction tests', () {
testWidgets('has correct block selection on tap option button',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
// We edit the document by entering some characters, to ensure the document has focus
await tester.editor.updateSelection(
Selection.collapsed(Position(path: [2])),
);
// Insert character 'a' three times - easy to identify
await tester.ime.insertText('aaa');
await tester.pumpAndSettle();
final editorState = tester.editor.getCurrentEditorState();
final node = editorState.getNodeAtPath([2]);
expect(node?.delta?.toPlainText(), startsWith('aaa'));
final multiSelection = Selection(
start: Position(path: [2], offset: 3),
end: Position(path: [4], offset: 40),
);
// Select multiple items
await tester.editor.updateSelection(multiSelection);
await tester.pumpAndSettle();
// Press the block option menu
await tester.editor.hoverAndClickOptionMenuButton([2]);
await tester.pumpAndSettle();
// Expect the selection to be Block type and not have changed
expect(editorState.selectionType, SelectionType.block);
expect(editorState.selection, multiSelection);
});
});
}
@@ -8,6 +8,7 @@ import 'document_with_file_test.dart' as document_with_file_test;
import 'document_with_image_block_test.dart' as document_with_image_block_test;
import 'document_with_multi_image_block_test.dart'
as document_with_multi_image_block_test;
import 'document_block_option_test.dart' as document_block_option_test;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
@@ -19,7 +20,5 @@ void main() {
document_more_actions_test.main();
document_with_file_test.main();
document_shortcuts_test.main();
// Disable subPage test temporarily, enable it in version 0.7.2
// document_sub_page_test.main();
document_block_option_test.main();
}
@@ -13,6 +13,7 @@ import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/remi
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_result/appflowy_result.dart';
import 'package:calendar_view/calendar_view.dart';
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart'
@@ -54,59 +55,18 @@ class DateCellEditorBloc
didReceiveCellUpdate: (DateCellDataPB? cellData) {
final dateCellData = DateCellData.fromPB(cellData);
ReminderOption option = state.reminderOption;
final cellReminderId = dateCellData.reminderId;
ReminderOption reminderOption = ReminderOption.none;
if (dateCellData.dateTime != null &&
state.reminderId.isEmpty &&
state.reminderOption != ReminderOption.none &&
cellReminderId.isNotEmpty) {
final date = state.reminderOption.withoutTime
? dateCellData.dateTime!.withoutTime
: dateCellData.dateTime!;
// Add Reminder
_reminderBloc.add(
ReminderEvent.addById(
reminderId: dateCellData.reminderId,
objectId: cellController.viewId,
meta: {
ReminderMetaKeys.includeTime: true.toString(),
ReminderMetaKeys.rowId: cellController.rowId,
},
scheduledAt: Int64(
state.reminderOption
.getNotificationDateTime(date)
.millisecondsSinceEpoch ~/
1000,
),
),
);
}
if (cellReminderId.isNotEmpty && dateCellData.dateTime != null) {
if (option.requiresNoTime && dateCellData.includeTime) {
option = ReminderOption.atTimeOfEvent;
} else if (!option.withoutTime && !dateCellData.includeTime) {
option = ReminderOption.onDayOfEvent;
if (dateCellData.reminderId.isNotEmpty &&
dateCellData.dateTime != null) {
final reminder = _reminderBloc.state.reminders
.firstWhereOrNull((r) => r.id == dateCellData.reminderId);
if (reminder != null) {
reminderOption = ReminderOption.fromDateDifference(
dateCellData.dateTime!,
reminder.scheduledAt.toDateTime(),
);
}
final date = option.withoutTime
? dateCellData.dateTime!.withoutTime
: dateCellData.dateTime!;
final scheduledAt = option.getNotificationDateTime(date);
// Update Reminder
_reminderBloc.add(
ReminderEvent.update(
ReminderUpdate(
id: dateCellData.reminderId,
scheduledAt: scheduledAt,
includeTime: true,
),
),
);
}
emit(
@@ -116,7 +76,7 @@ class DateCellEditorBloc
includeTime: dateCellData.includeTime,
isRange: dateCellData.isRange,
reminderId: dateCellData.reminderId,
reminderOption: option,
reminderOption: reminderOption,
),
);
},
@@ -158,81 +118,55 @@ class DateCellEditorBloc
await _clearDate();
},
setReminderOption: (
ReminderOption option,
) async {
if (option == ReminderOption.none && state.reminderId.isNotEmpty) {
_reminderBloc.add(
ReminderEvent.remove(reminderId: state.reminderId),
);
await _updateDateData(reminderId: "");
} else if (state.reminderId.isEmpty) {
// New Reminder
final reminderId = nanoid();
await _updateDateData(
reminderId: reminderId,
date: state.dateTime ?? DateTime.now().withoutTime,
);
} else if (state.dateTime != null) {
// Update reminder
final scheduledAt =
option.getNotificationDateTime(state.dateTime!);
_reminderBloc.add(
ReminderEvent.update(
ReminderUpdate(
id: state.reminderId,
scheduledAt: scheduledAt,
includeTime: true,
),
),
);
}
},
removeReminder: () async {
await _updateDateData(reminderId: "");
setReminderOption: (ReminderOption option) async {
await _setReminderOption(option);
},
);
},
);
}
Future<void> _updateDateData({
Future<FlowyResult<void, FlowyError>> _updateDateData({
DateTime? date,
DateTime? endDate,
String? reminderId,
bool updateReminderIfNecessary = true,
}) async {
final result = await _dateCellBackendService.update(
date: date,
endDate: endDate,
reminderId: reminderId,
);
result.onFailure(Log.error);
if (updateReminderIfNecessary) {
result.onSuccess((_) => _updateReminderIfNecessary(date));
}
return result;
}
Future<void> _updateIsRange(
bool isRange,
DateTime? dateTime,
DateTime? endDateTime,
) async {
final result = await _dateCellBackendService.update(
date: dateTime,
endDate: endDateTime,
isRange: isRange,
);
result.onFailure(Log.error);
) {
return _dateCellBackendService
.update(
date: dateTime,
endDate: endDateTime,
isRange: isRange,
)
.fold((s) => _updateReminderIfNecessary(dateTime), Log.error);
}
Future<void> _updateIncludeTime(
bool includeTime,
DateTime? dateTime,
DateTime? endDateTime,
) async {
final result = await _dateCellBackendService.update(
date: dateTime,
endDate: endDateTime,
includeTime: includeTime,
);
result.onFailure(Log.error);
) {
return _dateCellBackendService
.update(
date: dateTime,
endDate: endDateTime,
includeTime: includeTime,
)
.fold((s) => _updateReminderIfNecessary(dateTime), Log.error);
}
Future<void> _clearDate() async {
@@ -240,6 +174,98 @@ class DateCellEditorBloc
result.onFailure(Log.error);
}
Future<void> _setReminderOption(ReminderOption option) async {
if (state.reminderId.isEmpty) {
if (option == ReminderOption.none) {
// do nothing
return;
}
// if no date, fill it first
final fillerDateTime =
state.includeTime ? DateTime.now() : DateTime.now().withoutTime;
if (state.dateTime == null) {
final result = await _updateDateData(
date: fillerDateTime,
endDate: state.isRange ? fillerDateTime : null,
updateReminderIfNecessary: false,
);
// return if filling date is unsuccessful
if (result.isFailure) {
return;
}
}
// create a reminder
final reminderId = nanoid();
await _updateCellReminderId(reminderId);
final dateTime = state.dateTime ?? fillerDateTime;
_reminderBloc.add(
ReminderEvent.addById(
reminderId: reminderId,
objectId: cellController.viewId,
meta: {
ReminderMetaKeys.includeTime: state.includeTime.toString(),
ReminderMetaKeys.rowId: cellController.rowId,
},
scheduledAt: Int64(
option.getNotificationDateTime(dateTime).millisecondsSinceEpoch ~/
1000,
),
),
);
} else {
if (option == ReminderOption.none) {
// remove reminder from reminder bloc and cell data
_reminderBloc.add(ReminderEvent.remove(reminderId: state.reminderId));
await _updateCellReminderId("");
} else {
// Update reminder
final scheduledAt = option.getNotificationDateTime(state.dateTime!);
_reminderBloc.add(
ReminderEvent.update(
ReminderUpdate(
id: state.reminderId,
scheduledAt: scheduledAt,
includeTime: state.includeTime,
),
),
);
}
}
}
Future<void> _updateCellReminderId(
String reminderId,
) async {
final result = await _dateCellBackendService.update(
reminderId: reminderId,
);
result.onFailure(Log.error);
}
void _updateReminderIfNecessary(
DateTime? dateTime,
) {
if (state.reminderId.isEmpty ||
state.reminderOption == ReminderOption.none ||
dateTime == null) {
return;
}
final scheduledAt = state.reminderOption.getNotificationDateTime(dateTime);
// Update Reminder
_reminderBloc.add(
ReminderEvent.update(
ReminderUpdate(
id: state.reminderId,
scheduledAt: scheduledAt,
includeTime: state.includeTime,
),
),
);
}
String timeFormatPrompt(FlowyError error) {
return switch (state.dateTypeOptionPB.timeFormat) {
TimeFormatPB.TwelveHour =>
@@ -300,7 +326,7 @@ class DateCellEditorBloc
typeOptionData: newDateTypeOption.writeToBuffer(),
);
result.fold((_) => {}, (err) => Log.error(err));
result.onFailure(Log.error);
}
}
@@ -338,8 +364,6 @@ class DateCellEditorEvent with _$DateCellEditorEvent {
const factory DateCellEditorEvent.setReminderOption(ReminderOption option) =
_SetReminderOption;
const factory DateCellEditorEvent.removeReminder() = _RemoveReminder;
// date field type options are modified
const factory DateCellEditorEvent.setTimeFormat(TimeFormatPB timeFormat) =
_SetTimeFormat;
@@ -55,6 +55,14 @@ class ChecklistRowDetailCell extends StatefulWidget {
}
class _ChecklistRowDetailCellState extends State<ChecklistRowDetailCell> {
final phantomTextController = TextEditingController();
@override
void dispose() {
phantomTextController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Align(
@@ -69,6 +77,7 @@ class _ChecklistRowDetailCellState extends State<ChecklistRowDetailCell> {
),
const VSpace(2.0),
_ChecklistItems(
phantomTextController: phantomTextController,
onStartCreatingTaskAfter: (index) {
context
.read<ChecklistCellBloc>()
@@ -80,6 +89,7 @@ class _ChecklistRowDetailCellState extends State<ChecklistRowDetailCell> {
onTap: () {
final bloc = context.read<ChecklistCellBloc>();
if (bloc.state.phantomIndex == null) {
phantomTextController.clear();
bloc.add(
ChecklistCellEvent.updatePhantomIndex(
bloc.state.showIncompleteOnly
@@ -89,6 +99,13 @@ class _ChecklistRowDetailCellState extends State<ChecklistRowDetailCell> {
: bloc.state.tasks.length,
),
);
} else {
bloc.add(
ChecklistCellEvent.createNewTask(
phantomTextController.text,
index: bloc.state.phantomIndex,
),
);
}
},
),
@@ -153,9 +170,11 @@ class ProgressAndHideCompleteButton extends StatelessWidget {
class _ChecklistItems extends StatelessWidget {
const _ChecklistItems({
required this.phantomTextController,
required this.onStartCreatingTaskAfter,
});
final TextEditingController phantomTextController;
final void Function(int index) onStartCreatingTaskAfter;
@override
@@ -165,6 +184,7 @@ class _ChecklistItems extends StatelessWidget {
_CancelCreatingFromPhantomIntent:
CallbackAction<_CancelCreatingFromPhantomIntent>(
onInvoke: (_CancelCreatingFromPhantomIntent intent) {
phantomTextController.clear();
context
.read<ChecklistCellBloc>()
.add(const ChecklistCellEvent.updatePhantomIndex(null));
@@ -240,7 +260,10 @@ class _ChecklistItems extends StatelessWidget {
Padding(
key: const ValueKey('new_checklist_cell_task'),
padding: const EdgeInsets.symmetric(vertical: 2.0),
child: PhantomChecklistItem(index: state.phantomIndex!),
child: PhantomChecklistItem(
index: state.phantomIndex!,
textController: phantomTextController,
),
),
);
}
@@ -268,16 +291,17 @@ class PhantomChecklistItem extends StatefulWidget {
const PhantomChecklistItem({
super.key,
required this.index,
required this.textController,
});
final int index;
final TextEditingController textController;
@override
State<PhantomChecklistItem> createState() => _PhantomChecklistItemState();
}
class _PhantomChecklistItemState extends State<PhantomChecklistItem> {
TextEditingController textController = TextEditingController();
final focusNode = FocusNode();
bool isComposing = false;
@@ -285,18 +309,19 @@ class _PhantomChecklistItemState extends State<PhantomChecklistItem> {
@override
void initState() {
super.initState();
textController.addListener(_onTextChanged);
widget.textController.addListener(_onTextChanged);
focusNode.addListener(_onFocusChanged);
WidgetsBinding.instance
.addPostFrameCallback((_) => focusNode.requestFocus());
}
void _onTextChanged() =>
setState(() => isComposing = !textController.value.composing.isCollapsed);
void _onTextChanged() => setState(
() => isComposing = !widget.textController.value.composing.isCollapsed,
);
void _onFocusChanged() {
if (!focusNode.hasFocus) {
textController.clear();
widget.textController.clear();
Actions.maybeInvoke(
context,
const _CancelCreatingFromPhantomIntent(),
@@ -306,8 +331,7 @@ class _PhantomChecklistItemState extends State<PhantomChecklistItem> {
@override
void dispose() {
textController.removeListener(_onTextChanged);
textController.dispose();
widget.textController.removeListener(_onTextChanged);
focusNode.removeListener(_onFocusChanged);
focusNode.dispose();
super.dispose();
@@ -319,15 +343,13 @@ class _PhantomChecklistItemState extends State<PhantomChecklistItem> {
actions: {
_SubmitPhantomTaskIntent: CallbackAction<_SubmitPhantomTaskIntent>(
onInvoke: (_SubmitPhantomTaskIntent intent) {
if (intent.taskDescription.isNotEmpty) {
context.read<ChecklistCellBloc>().add(
ChecklistCellEvent.createNewTask(
intent.taskDescription,
index: intent.index,
),
);
}
textController.clear();
context.read<ChecklistCellBloc>().add(
ChecklistCellEvent.createNewTask(
intent.taskDescription,
index: intent.index,
),
);
widget.textController.clear();
return;
},
),
@@ -342,11 +364,11 @@ class _PhantomChecklistItemState extends State<PhantomChecklistItem> {
),
child: Center(
child: ChecklistCellTextfield(
textController: textController,
textController: widget.textController,
focusNode: focusNode,
contentPadding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 10,
vertical: 8,
),
),
),
@@ -361,7 +383,7 @@ class _PhantomChecklistItemState extends State<PhantomChecklistItem> {
: {
const SingleActivator(LogicalKeyboardKey.enter):
_SubmitPhantomTaskIntent(
taskDescription: textController.text,
taskDescription: widget.textController.text,
index: widget.index,
),
const SingleActivator(LogicalKeyboardKey.escape):
@@ -386,39 +408,41 @@ class ChecklistItemControl extends StatelessWidget {
return ChangeNotifierProvider.value(
value: cellNotifer,
child: Consumer<CellContainerNotifier>(
builder: (buildContext, notifier, _) => GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: Container(
margin: const EdgeInsets.fromLTRB(8.0, 2.0, 8.0, 0),
height: 12,
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 150),
child: notifier.isHover
? FlowyTooltip(
message: LocaleKeys.grid_checklist_addNew.tr(),
child: Row(
children: [
const Flexible(child: Center(child: Divider())),
const HSpace(12.0),
FilledButton(
style: FilledButton.styleFrom(
minimumSize: const Size.square(12),
maximumSize: const Size.square(12),
padding: EdgeInsets.zero,
builder: (buildContext, notifier, _) => TextFieldTapRegion(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: Container(
margin: const EdgeInsets.fromLTRB(8.0, 2.0, 8.0, 0),
height: 12,
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 150),
child: notifier.isHover
? FlowyTooltip(
message: LocaleKeys.grid_checklist_addNew.tr(),
child: Row(
children: [
const Flexible(child: Center(child: Divider())),
const HSpace(12.0),
FilledButton(
style: FilledButton.styleFrom(
minimumSize: const Size.square(12),
maximumSize: const Size.square(12),
padding: EdgeInsets.zero,
),
onPressed: onTap,
child: FlowySvg(
FlowySvgs.add_s,
color: Theme.of(context).colorScheme.onPrimary,
),
),
onPressed: onTap,
child: FlowySvg(
FlowySvgs.add_s,
color: Theme.of(context).colorScheme.onPrimary,
),
),
const HSpace(12.0),
const Flexible(child: Center(child: Divider())),
],
),
)
: const SizedBox.expand(),
const HSpace(12.0),
const Flexible(child: Center(child: Divider())),
],
),
)
: const SizedBox.expand(),
),
),
),
),
@@ -442,7 +442,7 @@ class _NewTaskItemState extends State<NewTaskItem> {
? Theme.of(context).colorScheme.primaryContainer
: Theme.of(context).disabledColor,
fontColor: Theme.of(context).colorScheme.onPrimary,
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
onPressed: isCreateButtonEnabled
? () {
context.read<ChecklistCellBloc>().add(
@@ -42,7 +42,7 @@ class ChecklistCellTextfield extends StatelessWidget {
required this.focusNode,
this.onChanged,
this.contentPadding = const EdgeInsets.symmetric(
vertical: 10,
vertical: 8,
horizontal: 2,
),
this.onSubmitted,
@@ -1,5 +1,7 @@
import 'package:appflowy/core/helpers/url_launcher.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_media_upload.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/widgets/flowy_option_tile.dart';
import 'package:appflowy/plugins/base/drag_handler.dart';
@@ -10,7 +12,9 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/file/file_
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/common.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/image_render.dart';
import 'package:appflowy/workspace/presentation/widgets/image_viewer/interactive_image_viewer.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/file_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/media_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
@@ -25,10 +29,6 @@ class MobileMediaCellEditor extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(12),
),
constraints: const BoxConstraints.tightFor(height: 420),
child: BlocProvider.value(
value: context.read<MediaCellBloc>(),
@@ -47,10 +47,28 @@ class MobileMediaCellEditor extends StatelessWidget {
fontSize: 18,
),
),
const Positioned(
Positioned(
top: 8,
right: 18,
child: FlowySvg(FlowySvgs.add_m, size: Size.square(28)),
child: GestureDetector(
onTap: () => showMobileBottomSheet(
context,
title: LocaleKeys.grid_media_addFileMobile.tr(),
showHeader: true,
showCloseButton: true,
showDragHandle: true,
builder: (dContext) => BlocProvider.value(
value: context.read<MediaCellBloc>(),
child: MobileMediaUploadSheetContent(
dialogContext: dContext,
),
),
),
child: const FlowySvg(
FlowySvgs.add_m,
size: Size.square(28),
),
),
),
],
),
@@ -60,33 +78,6 @@ class MobileMediaCellEditor extends StatelessWidget {
child: SingleChildScrollView(
child: Column(
children: [
// Padding(
// padding: const EdgeInsets.all(8),
// child: FlowyButton(
// margin: const EdgeInsets.all(12),
// onTap: () => showMobileBottomSheet(
// context,
// title: LocaleKeys.grid_media_addFileMobile.tr(),
// showHeader: true,
// showCloseButton: true,
// showDragHandle: true,
// builder: (dContext) =>
// MobileMediaUploadSheetContent(
// dialogContext: dContext,
// ),
// ),
// text: const Row(
// children: [
// FlowySvg(
// FlowySvgs.add_s,
// size: Size.square(20),
// ),
// HSpace(8),
// FlowyText('Add a file or image', fontSize: 15),
// ],
// ),
// ),
// ),
if (state.files.isNotEmpty) const Divider(height: .5),
...state.files.map(
(file) => Padding(
@@ -115,27 +106,41 @@ class _FileItem extends StatelessWidget {
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.secondaryContainer,
color: Theme.of(context).scaffoldBackgroundColor,
),
child: ListTile(
contentPadding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 16,
),
title: Row(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: file.fileType == MediaFileTypePB.Image
? CrossAxisAlignment.start
: CrossAxisAlignment.center,
children: [
if (file.fileType != MediaFileTypePB.Image) ...[
FlowySvg(file.fileType.icon, size: const Size.square(24)),
const HSpace(12),
Expanded(
child: FlowyText(
file.name,
overflow: TextOverflow.ellipsis,
Flexible(
child: GestureDetector(
onTap: () => afLaunchUrlString(file.url),
child: Row(
children: [
FlowySvg(file.fileType.icon, size: const Size.square(24)),
const HSpace(12),
Expanded(
child: FlowyText(
file.name,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
),
] else ...[
Expanded(
child: Container(
margin: const EdgeInsets.symmetric(vertical: 12),
alignment: Alignment.centerLeft,
constraints: const BoxConstraints(maxHeight: 125),
constraints: const BoxConstraints(maxHeight: 96),
child: GestureDetector(
onTap: () => openInteractiveViewer(context),
child: ImageRender(
@@ -153,13 +158,14 @@ class _FileItem extends StatelessWidget {
),
],
FlowyIconButton(
width: 40,
width: 20,
icon: const FlowySvg(
FlowySvgs.three_dots_s,
size: Size.square(20),
),
onPressed: () => showMobileBottomSheet(
context,
backgroundColor: Theme.of(context).colorScheme.surface,
showDragHandle: true,
builder: (_) => BlocProvider.value(
value: context.read<MediaCellBloc>(),
@@ -189,10 +195,10 @@ class _EditFileSheet extends StatefulWidget {
final MediaFilePB file;
@override
State<_EditFileSheet> createState() => __EditFileSheetState();
State<_EditFileSheet> createState() => _EditFileSheetState();
}
class __EditFileSheetState extends State<_EditFileSheet> {
class _EditFileSheetState extends State<_EditFileSheet> {
late final controller = TextEditingController(text: widget.file.name);
Loading? loader;
@@ -211,15 +217,9 @@ class __EditFileSheetState extends State<_EditFileSheet> {
child: Column(
children: [
const VSpace(16),
// _FileTextField(
// file: file,
// controller: controller,
// onChanged: (name) =>
// context.read<MediaCellBloc>().renameFile(file.id, name),
// ),
const VSpace(20),
if (file.fileType == MediaFileTypePB.Image)
if (file.fileType == MediaFileTypePB.Image) ...[
FlowyOptionTile.text(
showTopBorder: false,
text: LocaleKeys.grid_media_expand.tr(),
leftIcon: const FlowySvg(
FlowySvgs.full_view_s,
@@ -227,28 +227,62 @@ class __EditFileSheetState extends State<_EditFileSheet> {
),
onTap: () => openInteractiveViewer(context),
),
FlowyOptionTile.text(
showTopBorder: false,
text: LocaleKeys.grid_media_setAsCover.tr(),
leftIcon: const FlowySvg(
FlowySvgs.cover_s,
size: Size.square(20),
),
onTap: () => context.read<MediaCellBloc>().add(
MediaCellEvent.setCover(
RowCoverPB(
data: file.url,
uploadType: file.uploadType,
coverType: CoverTypePB.FileCover,
),
),
),
),
],
FlowyOptionTile.text(
text: file.fileType == MediaFileTypePB.Link
? LocaleKeys.grid_media_expand.tr()
: LocaleKeys.grid_media_download.tr(),
leftIcon: FlowySvg(
file.fileType == MediaFileTypePB.Link
? FlowySvgs.m_link_m
: FlowySvgs.import_s,
size: const Size.square(20),
),
onTap: () async => downloadMediaFile(
context,
widget.file,
userProfile: context.read<MediaCellBloc>().state.userProfile,
onDownloadBegin: () {
loader?.stop();
loader = Loading(context);
loader?.start();
},
onDownloadEnd: () => loader?.stop(),
showTopBorder: file.fileType == MediaFileTypePB.Image,
text: LocaleKeys.grid_media_openInBrowser.tr(),
leftIcon: const FlowySvg(
FlowySvgs.open_in_browser_s,
size: Size.square(20),
),
onTap: () => afLaunchUrlString(file.url),
),
// TODO(Mathias): Rename interaction need design
// FlowyOptionTile.text(
// text: LocaleKeys.grid_media_rename.tr(),
// leftIcon: const FlowySvg(
// FlowySvgs.rename_s,
// size: Size.square(20),
// ),
// onTap: () {},
// ),
if (widget.file.uploadType == FileUploadTypePB.CloudFile) ...[
FlowyOptionTile.text(
onTap: () async => downloadMediaFile(
context,
file,
userProfile: context.read<MediaCellBloc>().state.userProfile,
onDownloadBegin: () {
loader?.stop();
loader = Loading(context);
loader?.start();
},
onDownloadEnd: () => loader?.stop(),
),
text: LocaleKeys.button_download.tr(),
leftIcon: const FlowySvg(
FlowySvgs.save_as_s,
size: Size.square(20),
),
),
],
FlowyOptionTile.text(
text: LocaleKeys.grid_media_delete.tr(),
textColor: Theme.of(context).colorScheme.error,
@@ -30,8 +30,10 @@ class OptionButton extends StatefulWidget {
}
class _OptionButtonState extends State<OptionButton> {
late final registerKey =
_interceptorKey + widget.blockComponentContext.node.id;
late final gestureInterceptor = SelectionGestureInterceptor(
key: _interceptorKey,
key: registerKey,
canTap: (details) => !_isTapInBounds(details.globalPosition),
);
@@ -52,7 +54,7 @@ class _OptionButtonState extends State<OptionButton> {
@override
void dispose() {
widget.editorState.service.selectionService.unregisterGestureInterceptor(
_interceptorKey,
registerKey,
);
super.dispose();
@@ -640,6 +640,7 @@ SelectionMenuItem subPageSlashMenuItem = SelectionMenuItem.node(
LocaleKeys.document_slashMenu_subPage_keyword5.tr(),
LocaleKeys.document_slashMenu_subPage_keyword6.tr(),
LocaleKeys.document_slashMenu_subPage_keyword7.tr(),
LocaleKeys.document_slashMenu_subPage_keyword8.tr(),
],
updateSelection: (_, path, __, ___) =>
Selection.collapsed(Position(path: path)),
@@ -67,6 +67,7 @@ Future<void> _formatGreaterToToggleHeading(
node.path,
toggleListBlockNode(
delta: delta,
children: node.children.map((e) => e.copyWith()).toList(),
),
)
..deleteNode(node);
@@ -37,7 +37,7 @@ class FileStorageService {
final fileProgress = FileProgress.fromJsonString(event);
if (fileProgress != null) {
Log.debug(
"Upload progress: file: ${fileProgress.fileUrl} ${fileProgress.progress}",
"FileStorageService upload file: ${fileProgress.fileUrl} ${fileProgress.progress}",
);
final notifier = _notifierList[fileProgress.fileUrl];
if (notifier != null) {
@@ -167,6 +167,10 @@ class SidebarPlanBloc extends Bloc<SidebarPlanEvent, SidebarPlanState> {
),
);
},
changedWorkspace: (workspaceId) {
emit(state.copyWith(workspaceId: workspaceId));
_checkWorkspaceUsage();
},
);
}
@@ -200,6 +204,10 @@ class SidebarPlanEvent with _$SidebarPlanEvent {
SidebarToastTierIndicator indicator,
) = _UpdateTierIndicator;
const factory SidebarPlanEvent.receiveError(FlowyError error) = _ReceiveError;
const factory SidebarPlanEvent.changedWorkspace({
required String workspaceId,
}) = _ChangedWorkspace;
}
@freezed
@@ -216,7 +224,6 @@ class SidebarPlanState with _$SidebarPlanState {
@freezed
class SidebarToastTierIndicator with _$SidebarToastTierIndicator {
// when start downloading the model
const factory SidebarToastTierIndicator.storageLimitHit() = _StorageLimitHit;
const factory SidebarToastTierIndicator.aiMaxiLimitHit() = _aiMaxLimitHit;
const factory SidebarToastTierIndicator.loading() = _Loading;
@@ -75,131 +75,140 @@ class HomeSideBar extends StatelessWidget {
// +-- Public Or Private Section: control the sections of the workspace
// |
// +-- Trash Section
return BlocConsumer<UserWorkspaceBloc, UserWorkspaceState>(
listenWhen: (previous, current) =>
previous.currentWorkspace?.workspaceId !=
current.currentWorkspace?.workspaceId,
listener: (context, state) {
if (FeatureFlag.search.isOn) {
// Notify command palette that workspace has changed
context.read<CommandPaletteBloc>().add(
CommandPaletteEvent.workspaceChanged(
workspaceId: state.currentWorkspace?.workspaceId,
),
);
}
return BlocProvider(
create: (context) => SidebarPlanBloc()
..add(SidebarPlanEvent.init(workspaceSetting.workspaceId, userProfile)),
child: BlocConsumer<UserWorkspaceBloc, UserWorkspaceState>(
listenWhen: (prev, curr) =>
prev.currentWorkspace?.workspaceId !=
curr.currentWorkspace?.workspaceId,
listener: (context, state) {
if (FeatureFlag.search.isOn) {
// Notify command palette that workspace has changed
context.read<CommandPaletteBloc>().add(
CommandPaletteEvent.workspaceChanged(
workspaceId: state.currentWorkspace?.workspaceId,
),
);
}
// Re-initialize workspace-specific services
getIt<CachedRecentService>().reset();
},
// Rebuild the whole sidebar when the current workspace changes
buildWhen: (previous, current) =>
previous.currentWorkspace?.workspaceId !=
current.currentWorkspace?.workspaceId,
builder: (context, state) {
if (state.currentWorkspace == null) {
return const SizedBox.shrink();
}
if (state.currentWorkspace != null) {
context.read<SidebarPlanBloc>().add(
SidebarPlanEvent.changedWorkspace(
workspaceId: state.currentWorkspace!.workspaceId,
),
);
}
final workspaceId =
state.currentWorkspace?.workspaceId ?? workspaceSetting.workspaceId;
return MultiBlocProvider(
providers: [
BlocProvider.value(value: getIt<ActionNavigationBloc>()),
BlocProvider(
create: (_) => SidebarSectionsBloc()
..add(SidebarSectionsEvent.initial(userProfile, workspaceId)),
),
BlocProvider(
create: (_) =>
SpaceBloc(userProfile: userProfile, workspaceId: workspaceId)
..add(const SpaceEvent.initial(openFirstPage: false)),
),
BlocProvider(
create: (_) => SidebarPlanBloc()
..add(SidebarPlanEvent.init(workspaceId, userProfile)),
),
],
child: MultiBlocListener(
listeners: [
BlocListener<SidebarSectionsBloc, SidebarSectionsState>(
listenWhen: (p, c) =>
p.lastCreatedRootView?.id != c.lastCreatedRootView?.id,
listener: (context, state) => context.read<TabsBloc>().add(
TabsEvent.openPlugin(
plugin: state.lastCreatedRootView!.plugin(),
// Re-initialize workspace-specific services
getIt<CachedRecentService>().reset();
},
// Rebuild the whole sidebar when the current workspace changes
buildWhen: (previous, current) =>
previous.currentWorkspace?.workspaceId !=
current.currentWorkspace?.workspaceId,
builder: (context, state) {
if (state.currentWorkspace == null) {
return const SizedBox.shrink();
}
final workspaceId = state.currentWorkspace?.workspaceId ??
workspaceSetting.workspaceId;
return MultiBlocProvider(
providers: [
BlocProvider.value(value: getIt<ActionNavigationBloc>()),
BlocProvider(
create: (_) => SidebarSectionsBloc()
..add(SidebarSectionsEvent.initial(userProfile, workspaceId)),
),
BlocProvider(
create: (_) => SpaceBloc(
userProfile: userProfile,
workspaceId: workspaceId,
)..add(const SpaceEvent.initial(openFirstPage: false)),
),
],
child: MultiBlocListener(
listeners: [
BlocListener<SidebarSectionsBloc, SidebarSectionsState>(
listenWhen: (p, c) =>
p.lastCreatedRootView?.id != c.lastCreatedRootView?.id,
listener: (context, state) => context.read<TabsBloc>().add(
TabsEvent.openPlugin(
plugin: state.lastCreatedRootView!.plugin(),
),
),
),
),
BlocListener<SpaceBloc, SpaceState>(
listenWhen: (p, c) =>
p.lastCreatedPage?.id != c.lastCreatedPage?.id ||
p.isDuplicatingSpace != c.isDuplicatingSpace,
listener: (context, state) {
final page = state.lastCreatedPage;
if (page == null || page.id.isEmpty) {
// open the blank page
context
.read<TabsBloc>()
.add(TabsEvent.openPlugin(plugin: BlankPagePlugin()));
} else {
context.read<TabsBloc>().add(
TabsEvent.openPlugin(
plugin: state.lastCreatedPage!.plugin(),
),
);
}
if (state.isDuplicatingSpace) {
_duplicateSpaceLoading ??= Loading(context);
_duplicateSpaceLoading?.start();
} else if (_duplicateSpaceLoading != null) {
_duplicateSpaceLoading?.stop();
_duplicateSpaceLoading = null;
}
},
),
BlocListener<ActionNavigationBloc, ActionNavigationState>(
listenWhen: (_, curr) => curr.action != null,
listener: _onNotificationAction,
),
BlocListener<UserWorkspaceBloc, UserWorkspaceState>(
listener: (context, state) {
final actionType = state.actionResult?.actionType;
if (actionType == UserWorkspaceActionType.create ||
actionType == UserWorkspaceActionType.delete ||
actionType == UserWorkspaceActionType.open) {
if (context.read<SpaceBloc>().state.spaces.isEmpty) {
context.read<SidebarSectionsBloc>().add(
SidebarSectionsEvent.reload(
userProfile,
state.currentWorkspace?.workspaceId ??
workspaceSetting.workspaceId,
),
);
),
BlocListener<SpaceBloc, SpaceState>(
listenWhen: (prev, curr) =>
prev.lastCreatedPage?.id != curr.lastCreatedPage?.id ||
prev.isDuplicatingSpace != curr.isDuplicatingSpace,
listener: (context, state) {
final page = state.lastCreatedPage;
if (page == null || page.id.isEmpty) {
// open the blank page
context
.read<TabsBloc>()
.add(TabsEvent.openPlugin(plugin: BlankPagePlugin()));
} else {
context.read<SpaceBloc>().add(
SpaceEvent.reset(
userProfile,
state.currentWorkspace?.workspaceId ??
workspaceSetting.workspaceId,
true,
context.read<TabsBloc>().add(
TabsEvent.openPlugin(
plugin: state.lastCreatedPage!.plugin(),
),
);
}
context
.read<FavoriteBloc>()
.add(const FavoriteEvent.fetchFavorites());
}
},
),
],
child: _Sidebar(userProfile: userProfile),
),
);
},
if (state.isDuplicatingSpace) {
_duplicateSpaceLoading ??= Loading(context);
_duplicateSpaceLoading?.start();
} else if (_duplicateSpaceLoading != null) {
_duplicateSpaceLoading?.stop();
_duplicateSpaceLoading = null;
}
},
),
BlocListener<ActionNavigationBloc, ActionNavigationState>(
listenWhen: (_, curr) => curr.action != null,
listener: _onNotificationAction,
),
BlocListener<UserWorkspaceBloc, UserWorkspaceState>(
listener: (context, state) {
final actionType = state.actionResult?.actionType;
if (actionType == UserWorkspaceActionType.create ||
actionType == UserWorkspaceActionType.delete ||
actionType == UserWorkspaceActionType.open) {
if (context.read<SpaceBloc>().state.spaces.isEmpty) {
context.read<SidebarSectionsBloc>().add(
SidebarSectionsEvent.reload(
userProfile,
state.currentWorkspace?.workspaceId ??
workspaceSetting.workspaceId,
),
);
} else {
context.read<SpaceBloc>().add(
SpaceEvent.reset(
userProfile,
state.currentWorkspace?.workspaceId ??
workspaceSetting.workspaceId,
true,
),
);
}
context
.read<FavoriteBloc>()
.add(const FavoriteEvent.fetchFavorites());
}
},
),
],
child: _Sidebar(userProfile: userProfile),
),
);
},
),
);
}
@@ -670,6 +670,13 @@ final _planLabels = [
_PlanItem(
label: LocaleKeys.settings_comparePlanDialog_planLabels_itemFileUpload.tr(),
),
_PlanItem(
label:
LocaleKeys.settings_comparePlanDialog_planLabels_customNamespace.tr(),
tooltip: LocaleKeys
.settings_comparePlanDialog_planLabels_customNamespaceTooltip
.tr(),
),
];
class _CellItem {
@@ -708,6 +715,9 @@ final List<_CellItem> _freeLabels = [
_CellItem(
label: LocaleKeys.settings_comparePlanDialog_freeLabels_itemFileUpload.tr(),
),
const _CellItem(
label: '',
),
];
final List<_CellItem> _proLabels = [
@@ -739,4 +749,8 @@ final List<_CellItem> _proLabels = [
_CellItem(
label: LocaleKeys.settings_comparePlanDialog_proLabels_itemFileUpload.tr(),
),
const _CellItem(
label: '',
icon: FlowySvgs.check_m,
),
];
@@ -42,11 +42,7 @@ class DomainItem extends StatelessWidget {
),
// Homepage
Expanded(
child: Padding(
padding: const EdgeInsets.only(
left: SettingsPageSitesConstants.alignPadding,),
child: _buildHomepage(context),
),
child: _buildHomepage(context),
),
// ... button
DomainMoreAction(namespace: namespace),
@@ -58,20 +54,23 @@ class DomainItem extends StatelessWidget {
return Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(right: 12.0),
child: FlowyButton(
useIntrinsicWidth: true,
text: FlowyText(
namespaceUrl,
fontSize: 14.0,
overflow: TextOverflow.ellipsis,
child: FlowyTooltip(
message: '${LocaleKeys.shareAction_visitSite.tr()}\n$namespaceUrl',
child: FlowyButton(
useIntrinsicWidth: true,
text: FlowyText(
namespaceUrl,
fontSize: 14.0,
overflow: TextOverflow.ellipsis,
),
onTap: () {
final namespaceUrl = ShareConstants.buildNamespaceUrl(
nameSpace: namespace,
withHttps: true,
);
afLaunchUrlString(namespaceUrl);
},
),
onTap: () {
final namespaceUrl = ShareConstants.buildNamespaceUrl(
nameSpace: namespace,
withHttps: true,
);
afLaunchUrlString(namespaceUrl);
},
),
);
}
@@ -85,7 +84,12 @@ class DomainItem extends StatelessWidget {
final isFreePlan = plan == WorkspacePlanPB.FreePlan;
if (isFreePlan) {
return const _FreePlanUpgradeButton();
return const Padding(
padding: EdgeInsets.only(
left: SettingsPageSitesConstants.alignPadding,
),
child: _FreePlanUpgradeButton(),
);
}
return const _HomePageButton();
@@ -178,7 +182,7 @@ class _HomePageButton extends StatelessWidget {
},
text: const FlowySvg(
FlowySvgs.close_m,
size: Size.square(18.0),
size: Size.square(19.0),
),
),
),
@@ -271,7 +271,7 @@ abstract class AppFlowyDatePickerState<T extends AppFlowyDatePicker>
setState(() {
isRange = value;
dateTime = newDateTime;
dateTime = focusedDateTime = newDateTime;
if (value) {
startDateTime = endDateTime = newDateTime;
} else {
@@ -228,12 +228,11 @@ class DesktopAppFlowyDatePickerState
@override
void onDateTimeInputSubmitted(DateTime value) {
refreshStartTextFieldNotifier.refresh();
if (isRange) {
DateTime end = endDateTime ?? value;
if (end.isBefore(value)) {
(value, end) = (end, value);
refreshEndTextFieldNotifier.refresh();
refreshStartTextFieldNotifier.refresh();
}
widget.onRangeSelected?.call(value, end);
@@ -255,7 +254,6 @@ class DesktopAppFlowyDatePickerState
@override
void onEndDateTimeInputSubmitted(DateTime value) {
refreshEndTextFieldNotifier.refresh();
if (isRange) {
if (endDateTime == null) {
value = combineDateTimes(value, widget.endDateTime);
@@ -263,7 +261,7 @@ class DesktopAppFlowyDatePickerState
DateTime start = startDateTime ?? value;
if (value.isBefore(start)) {
(start, value) = (value, start);
refreshStartTextFieldNotifier.refresh();
refreshEndTextFieldNotifier.refresh();
}
widget.onRangeSelected?.call(start, value);
@@ -124,10 +124,14 @@ enum ReminderOption {
required this.time,
this.withoutTime = false,
this.requiresNoTime = false,
});
}) : assert(!requiresNoTime || withoutTime);
final Duration time;
/// If true, don't consider the time component of the dateTime
final bool withoutTime;
/// If true, [withoutTime] must be true as well. Will add time instead of subtract to get notification time.
final bool requiresNoTime;
bool get timeExempt =>
@@ -190,10 +194,11 @@ enum ReminderOption {
_ => ReminderOption.custom,
};
DateTime getNotificationDateTime(DateTime date) => switch (withoutTime) {
true => requiresNoTime
DateTime getNotificationDateTime(DateTime date) {
return withoutTime
? requiresNoTime
? date.withoutTime.add(time)
: date.withoutTime.subtract(time),
_ => date.subtract(time),
};
: date.withoutTime.subtract(time)
: date.subtract(time);
}
}
+2 -2
View File
@@ -61,8 +61,8 @@ packages:
dependency: "direct main"
description:
path: "."
ref: "2903792"
resolved-ref: "2903792fa319e1b4077164eeb684f6e8d1c63e27"
ref: "76daa96"
resolved-ref: "76daa96af51f0ad4e881c10426a91780977544e5"
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
source: git
version: "4.0.0"
+1 -1
View File
@@ -172,7 +172,7 @@ dependency_overrides:
appflowy_editor:
git:
url: https://github.com/AppFlowy-IO/appflowy-editor.git
ref: "2903792"
ref: "76daa96"
appflowy_editor_plugins:
git:
@@ -2,7 +2,9 @@ import 'package:appflowy/plugins/database/application/cell/bloc/date_cell_editor
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
import 'package:appflowy/plugins/database/domain/field_service.dart';
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/reminder_selector.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:fixnum/fixnum.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:time/time.dart';
@@ -249,5 +251,141 @@ void main() {
TimeFormatPB.TwelveHour,
);
});
test('set reminder option', () async {
final reminderBloc = ReminderBloc();
final bloc = DateCellEditorBloc(
cellController: cellController,
reminderBloc: reminderBloc,
);
await gridResponseFuture();
expect(reminderBloc.state.reminders.length, 0);
final now = DateTime.now();
final threeDaysFromToday = DateTime(now.year, now.month, now.day + 3);
final fourDaysFromToday = DateTime(now.year, now.month, now.day + 4);
final fiveDaysFromToday = DateTime(now.year, now.month, now.day + 5);
bloc.add(DateCellEditorEvent.updateDateTime(threeDaysFromToday));
await gridResponseFuture();
bloc.add(
const DateCellEditorEvent.setReminderOption(
ReminderOption.onDayOfEvent,
),
);
await gridResponseFuture();
expect(reminderBloc.state.reminders.length, 1);
expect(
reminderBloc.state.reminders.first.scheduledAt,
Int64(
threeDaysFromToday
.add(const Duration(hours: 9))
.millisecondsSinceEpoch ~/
1000,
),
);
reminderBloc.add(const ReminderEvent.refresh());
await gridResponseFuture();
expect(reminderBloc.state.reminders.length, 1);
expect(
reminderBloc.state.reminders.first.scheduledAt,
Int64(
threeDaysFromToday
.add(const Duration(hours: 9))
.millisecondsSinceEpoch ~/
1000,
),
);
bloc.add(DateCellEditorEvent.updateDateTime(fourDaysFromToday));
await gridResponseFuture();
expect(reminderBloc.state.reminders.length, 1);
expect(
reminderBloc.state.reminders.first.scheduledAt,
Int64(
fourDaysFromToday
.add(const Duration(hours: 9))
.millisecondsSinceEpoch ~/
1000,
),
);
bloc.add(DateCellEditorEvent.updateDateTime(fiveDaysFromToday));
await gridResponseFuture();
reminderBloc.add(const ReminderEvent.refresh());
await gridResponseFuture();
expect(reminderBloc.state.reminders.length, 1);
expect(
reminderBloc.state.reminders.first.scheduledAt,
Int64(
fiveDaysFromToday
.add(const Duration(hours: 9))
.millisecondsSinceEpoch ~/
1000,
),
);
bloc.add(
const DateCellEditorEvent.setReminderOption(
ReminderOption.twoDaysBefore,
),
);
await gridResponseFuture();
expect(reminderBloc.state.reminders.length, 1);
expect(
reminderBloc.state.reminders.first.scheduledAt,
Int64(
threeDaysFromToday
.add(const Duration(hours: 9))
.millisecondsSinceEpoch ~/
1000,
),
);
bloc.add(
const DateCellEditorEvent.setReminderOption(ReminderOption.none),
);
await gridResponseFuture();
expect(reminderBloc.state.reminders.length, 0);
reminderBloc.add(const ReminderEvent.refresh());
await gridResponseFuture();
expect(reminderBloc.state.reminders.length, 0);
});
test('set reminder option from empty', () async {
final reminderBloc = ReminderBloc();
final bloc = DateCellEditorBloc(
cellController: cellController,
reminderBloc: reminderBloc,
);
await gridResponseFuture();
final now = DateTime.now();
final today = DateTime(now.year, now.month, now.day);
bloc.add(
const DateCellEditorEvent.setReminderOption(
ReminderOption.onDayOfEvent,
),
);
await gridResponseFuture();
expect(bloc.state.dateTime, today);
expect(reminderBloc.state.reminders.length, 1);
expect(
reminderBloc.state.reminders.first.scheduledAt,
Int64(
today.add(const Duration(hours: 9)).millisecondsSinceEpoch ~/ 1000,
),
);
bloc.add(const DateCellEditorEvent.clearDate());
await gridResponseFuture();
expect(reminderBloc.state.reminders.length, 0);
});
});
}
@@ -49,5 +49,40 @@ void main() {
editorState.dispose();
});
testWidgets('convert block contains children to toggle list',
(tester) async {
const paragraph1 = '>paragraph 1';
const paragraph1_1 = 'paragraph 1.1';
const paragraph1_2 = 'paragraph 1.2';
final document = createDocument([
paragraphNode(
text: paragraph1,
children: [
paragraphNode(text: paragraph1_1),
paragraphNode(text: paragraph1_2),
],
),
]);
final editorState = EditorState(document: document);
editorState.selection = Selection.collapsed(
Position(path: [0], offset: 1),
);
final result = await formatGreaterToToggleList.execute(editorState);
expect(result, true);
expect(editorState.document.root.children.length, 1);
final node = editorState.document.root.children[0];
expect(node.type, ToggleListBlockKeys.type);
expect(node.delta!.toPlainText(), 'paragraph 1');
expect(node.children.length, 2);
expect(node.children[0].delta!.toPlainText(), paragraph1_1);
expect(node.children[1].delta!.toPlainText(), paragraph1_2);
editorState.dispose();
});
});
}
@@ -17,7 +17,7 @@ bool FlutterWindow::OnCreate() {
RECT frame = GetClientArea();
// The size here must match the window dimensions to avoid unnecessary surface
// creation / destruction in the startup path.
// creation / destruction in the startup path.
flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
frame.right - frame.left, frame.bottom - frame.top, project_);
// Ensure that basic setup of the controller was successful.
@@ -26,6 +26,16 @@ bool FlutterWindow::OnCreate() {
}
RegisterPlugins(flutter_controller_->engine());
SetChildContent(flutter_controller_->view()->GetNativeWindow());
flutter_controller_->engine()->SetNextFrameCallback([&]() {
this->Show();
});
// Flutter can complete the first frame before the "show window" callback is
// registered. The following call ensures a frame is pending to ensure the
// window is shown. It is a no-op if the first frame hasn't completed yet.
flutter_controller_->ForceRedraw();
return true;
}
@@ -47,9 +47,12 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
FlutterWindow window(project);
Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720);
if (!window.CreateAndShow(L"AppFlowy", origin, size)) {
if (!window.Create(L"AppFlowy", origin, size)) {
return EXIT_FAILURE;
}
window.Show();
window.SetQuitOnClose(true);
::MSG msg;
@@ -1,60 +1,70 @@
#include "win32_window.h"
#include <dwmapi.h>
#include <flutter_windows.h>
#include "resource.h"
#include "app_links/app_links_plugin_c_api.h"
namespace
{
namespace {
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
/// Window attribute that enables dark mode window decorations.
///
/// Redefined in case the developer's machine has a Windows SDK older than
/// version 10.0.22000.0.
/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
#endif
// The number of Win32Window objects that currently exist.
static int g_active_window_count = 0;
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
/// Registry key for app theme preference.
///
/// A value of 0 indicates apps should use dark mode. A non-zero or missing
/// value indicates apps should use light mode.
constexpr const wchar_t kGetPreferredBrightnessRegKey[] =
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme";
// Scale helper to convert logical scaler values to physical using passed in
// scale factor
int Scale(int source, double scale_factor)
{
return static_cast<int>(source * scale_factor);
// The number of Win32Window objects that currently exist.
static int g_active_window_count = 0;
using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
// Scale helper to convert logical scaler values to physical using passed in
// scale factor
int Scale(int source, double scale_factor) {
return static_cast<int>(source * scale_factor);
}
// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
// This API is only needed for PerMonitor V1 awareness mode.
void EnableFullDpiSupportIfAvailable(HWND hwnd) {
HMODULE user32_module = LoadLibraryA("User32.dll");
if (!user32_module) {
return;
}
// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
// This API is only needed for PerMonitor V1 awareness mode.
void EnableFullDpiSupportIfAvailable(HWND hwnd)
{
HMODULE user32_module = LoadLibraryA("User32.dll");
if (!user32_module)
{
return;
}
auto enable_non_client_dpi_scaling =
reinterpret_cast<EnableNonClientDpiScaling *>(
GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
if (enable_non_client_dpi_scaling != nullptr)
{
enable_non_client_dpi_scaling(hwnd);
FreeLibrary(user32_module);
}
auto enable_non_client_dpi_scaling =
reinterpret_cast<EnableNonClientDpiScaling*>(
GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
if (enable_non_client_dpi_scaling != nullptr) {
enable_non_client_dpi_scaling(hwnd);
}
FreeLibrary(user32_module);
}
} // namespace
} // namespace
// Manages the Win32Window's window class registration.
class WindowClassRegistrar
{
public:
class WindowClassRegistrar {
public:
~WindowClassRegistrar() = default;
// Returns the singleton registar instance.
static WindowClassRegistrar *GetInstance()
{
if (!instance_)
{
// Returns the singleton registrar instance.
static WindowClassRegistrar* GetInstance() {
if (!instance_) {
instance_ = new WindowClassRegistrar();
}
return instance_;
@@ -62,26 +72,24 @@ public:
// Returns the name of the window class, registering the class if it hasn't
// previously been registered.
const wchar_t *GetWindowClass();
const wchar_t* GetWindowClass();
// Unregisters the window class. Should only be called if there are no
// instances of the window.
void UnregisterWindowClass();
private:
private:
WindowClassRegistrar() = default;
static WindowClassRegistrar *instance_;
static WindowClassRegistrar* instance_;
bool class_registered_ = false;
};
WindowClassRegistrar *WindowClassRegistrar::instance_ = nullptr;
WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
const wchar_t *WindowClassRegistrar::GetWindowClass()
{
if (!class_registered_)
{
const wchar_t* WindowClassRegistrar::GetWindowClass() {
if (!class_registered_) {
WNDCLASS window_class{};
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
window_class.lpszClassName = kWindowClassName;
@@ -100,35 +108,31 @@ const wchar_t *WindowClassRegistrar::GetWindowClass()
return kWindowClassName;
}
void WindowClassRegistrar::UnregisterWindowClass()
{
void WindowClassRegistrar::UnregisterWindowClass() {
UnregisterClass(kWindowClassName, nullptr);
class_registered_ = false;
}
Win32Window::Win32Window()
{
Win32Window::Win32Window() {
++g_active_window_count;
}
Win32Window::~Win32Window()
{
Win32Window::~Win32Window() {
--g_active_window_count;
Destroy();
}
bool Win32Window::CreateAndShow(const std::wstring &title,
const Point &origin,
const Size &size)
{
bool Win32Window::Create(const std::wstring& title,
const Point& origin,
const Size& size) {
Destroy();
if (SendAppLinkToInstance(title))
{
return false;
}
Destroy();
const wchar_t *window_class =
const wchar_t* window_class =
WindowClassRegistrar::GetInstance()->GetWindowClass();
const POINT target_point = {static_cast<LONG>(origin.x),
@@ -138,19 +142,158 @@ bool Win32Window::CreateAndShow(const std::wstring &title,
double scale_factor = dpi / 96.0;
HWND window = CreateWindow(
window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
window_class, title.c_str(), WS_OVERLAPPEDWINDOW,
Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
Scale(size.width, scale_factor), Scale(size.height, scale_factor),
nullptr, nullptr, GetModuleHandle(nullptr), this);
if (!window)
{
if (!window) {
return false;
}
UpdateTheme(window);
return OnCreate();
}
bool Win32Window::Show() {
return ShowWindow(window_handle_, SW_SHOWNORMAL);
}
// static
LRESULT CALLBACK Win32Window::WndProc(HWND const window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
if (message == WM_NCCREATE) {
auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
SetWindowLongPtr(window, GWLP_USERDATA,
reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
EnableFullDpiSupportIfAvailable(window);
that->window_handle_ = window;
} else if (Win32Window* that = GetThisFromHandle(window)) {
return that->MessageHandler(window, message, wparam, lparam);
}
return DefWindowProc(window, message, wparam, lparam);
}
LRESULT
Win32Window::MessageHandler(HWND hwnd,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
switch (message) {
case WM_DESTROY:
window_handle_ = nullptr;
Destroy();
if (quit_on_close_) {
PostQuitMessage(0);
}
return 0;
case WM_DPICHANGED: {
auto newRectSize = reinterpret_cast<RECT*>(lparam);
LONG newWidth = newRectSize->right - newRectSize->left;
LONG newHeight = newRectSize->bottom - newRectSize->top;
SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
return 0;
}
case WM_SIZE: {
RECT rect = GetClientArea();
if (child_content_ != nullptr) {
// Size and position the child window.
MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
rect.bottom - rect.top, TRUE);
}
return 0;
}
case WM_ACTIVATE:
if (child_content_ != nullptr) {
SetFocus(child_content_);
}
return 0;
case WM_DWMCOLORIZATIONCOLORCHANGED:
UpdateTheme(hwnd);
return 0;
}
return DefWindowProc(window_handle_, message, wparam, lparam);
}
void Win32Window::Destroy() {
OnDestroy();
if (window_handle_) {
DestroyWindow(window_handle_);
window_handle_ = nullptr;
}
if (g_active_window_count == 0) {
WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
}
}
Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
return reinterpret_cast<Win32Window*>(
GetWindowLongPtr(window, GWLP_USERDATA));
}
void Win32Window::SetChildContent(HWND content) {
child_content_ = content;
SetParent(content, window_handle_);
RECT frame = GetClientArea();
MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
frame.bottom - frame.top, true);
SetFocus(child_content_);
}
RECT Win32Window::GetClientArea() {
RECT frame;
GetClientRect(window_handle_, &frame);
return frame;
}
HWND Win32Window::GetHandle() {
return window_handle_;
}
void Win32Window::SetQuitOnClose(bool quit_on_close) {
quit_on_close_ = quit_on_close;
}
bool Win32Window::OnCreate() {
// No-op; provided for subclasses.
return true;
}
void Win32Window::OnDestroy() {
// No-op; provided for subclasses.
}
void Win32Window::UpdateTheme(HWND const window) {
DWORD light_mode;
DWORD light_mode_size = sizeof(light_mode);
LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey,
kGetPreferredBrightnessRegValue,
RRF_RT_REG_DWORD, nullptr, &light_mode,
&light_mode_size);
if (result == ERROR_SUCCESS) {
BOOL enable_dark_mode = light_mode == 0;
DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE,
&enable_dark_mode, sizeof(enable_dark_mode));
}
}
bool Win32Window::SendAppLinkToInstance(const std::wstring &title)
{
// Find our exact window
@@ -186,140 +329,4 @@ bool Win32Window::SendAppLinkToInstance(const std::wstring &title)
}
return false;
}
// static
LRESULT CALLBACK Win32Window::WndProc(HWND const window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept
{
if (message == WM_NCCREATE)
{
auto window_struct = reinterpret_cast<CREATESTRUCT *>(lparam);
SetWindowLongPtr(window, GWLP_USERDATA,
reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
auto that = static_cast<Win32Window *>(window_struct->lpCreateParams);
EnableFullDpiSupportIfAvailable(window);
that->window_handle_ = window;
}
else if (Win32Window *that = GetThisFromHandle(window))
{
return that->MessageHandler(window, message, wparam, lparam);
}
return DefWindowProc(window, message, wparam, lparam);
}
LRESULT
Win32Window::MessageHandler(HWND hwnd,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept
{
switch (message)
{
case WM_DESTROY:
window_handle_ = nullptr;
Destroy();
if (quit_on_close_)
{
PostQuitMessage(0);
}
return 0;
case WM_DPICHANGED:
{
auto newRectSize = reinterpret_cast<RECT *>(lparam);
LONG newWidth = newRectSize->right - newRectSize->left;
LONG newHeight = newRectSize->bottom - newRectSize->top;
SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
return 0;
}
case WM_SIZE:
{
RECT rect = GetClientArea();
if (child_content_ != nullptr)
{
// Size and position the child window.
MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
rect.bottom - rect.top, TRUE);
}
return 0;
}
case WM_ACTIVATE:
if (child_content_ != nullptr)
{
SetFocus(child_content_);
}
return 0;
}
return DefWindowProc(window_handle_, message, wparam, lparam);
}
void Win32Window::Destroy()
{
OnDestroy();
if (window_handle_)
{
DestroyWindow(window_handle_);
window_handle_ = nullptr;
}
if (g_active_window_count == 0)
{
WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
}
}
Win32Window *Win32Window::GetThisFromHandle(HWND const window) noexcept
{
return reinterpret_cast<Win32Window *>(
GetWindowLongPtr(window, GWLP_USERDATA));
}
void Win32Window::SetChildContent(HWND content)
{
child_content_ = content;
SetParent(content, window_handle_);
RECT frame = GetClientArea();
MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
frame.bottom - frame.top, true);
SetFocus(child_content_);
}
RECT Win32Window::GetClientArea()
{
RECT frame;
GetClientRect(window_handle_, &frame);
return frame;
}
HWND Win32Window::GetHandle()
{
return window_handle_;
}
void Win32Window::SetQuitOnClose(bool quit_on_close)
{
quit_on_close_ = quit_on_close;
}
bool Win32Window::OnCreate()
{
// No-op; provided for subclasses.
return true;
}
void Win32Window::OnDestroy()
{
// No-op; provided for subclasses.
}
}
@@ -10,18 +10,15 @@
// A class abstraction for a high DPI-aware Win32 Window. Intended to be
// inherited from by classes that wish to specialize with custom
// rendering and input handling
class Win32Window
{
public:
struct Point
{
class Win32Window {
public:
struct Point {
unsigned int x;
unsigned int y;
Point(unsigned int x, unsigned int y) : x(x), y(y) {}
};
struct Size
{
struct Size {
unsigned int width;
unsigned int height;
Size(unsigned int width, unsigned int height)
@@ -31,19 +28,16 @@ public:
Win32Window();
virtual ~Win32Window();
// Creates and shows a win32 window with |title| and position and size using
// Creates a win32 window with |title| that is positioned and sized using
// |origin| and |size|. New windows are created on the default monitor. Window
// sizes are specified to the OS in physical pixels, hence to ensure a
// consistent size to will treat the width height passed in to this function
// as logical pixels and scale to appropriate for the default monitor. Returns
// true if the window was created successfully.
bool CreateAndShow(const std::wstring &title,
const Point &origin,
const Size &size);
// consistent size this function will scale the inputted width and height as
// as appropriate for the default monitor. The window is invisible until
// |Show| is called. Returns true if the window was created successfully.
bool Create(const std::wstring& title, const Point& origin, const Size& size);
// Dispatches link if any.
// This method enables our app to be with a single instance too.
bool SendAppLinkToInstance(const std::wstring &title);
// Show the current window. Returns true if the window was successfully shown.
bool Show();
// Release OS resources associated with window.
void Destroy();
@@ -61,7 +55,11 @@ public:
// Return a RECT representing the bounds of the current client area.
RECT GetClientArea();
protected:
// Dispatches link if any.
// This method enables our app to be with a single instance too.
bool SendAppLinkToInstance(const std::wstring &title);
protected:
// Processes and route salient window messages for mouse handling,
// size change and DPI. Delegates handling of these to member overloads that
// inheriting classes can handle.
@@ -77,13 +75,13 @@ protected:
// Called when Destroy is called.
virtual void OnDestroy();
private:
private:
friend class WindowClassRegistrar;
// OS callback called by message pump. Handles the WM_NCCREATE message which
// is passed when the non-client area is being created and enables automatic
// non-client DPI scaling so that the non-client area automatically
// responsponds to changes in DPI. All other messages are handled by
// responds to changes in DPI. All other messages are handled by
// MessageHandler.
static LRESULT CALLBACK WndProc(HWND const window,
UINT const message,
@@ -91,7 +89,10 @@ private:
LPARAM const lparam) noexcept;
// Retrieves a class instance pointer for |window|
static Win32Window *GetThisFromHandle(HWND const window) noexcept;
static Win32Window* GetThisFromHandle(HWND const window) noexcept;
// Update the window frame's theme to match the system theme.
static void UpdateTheme(HWND const window);
bool quit_on_close_ = false;
@@ -102,4 +103,4 @@ private:
HWND child_content_ = nullptr;
};
#endif // RUNNER_WIN32_WINDOW_H_
#endif // RUNNER_WIN32_WINDOW_H_
+20 -20
View File
@@ -172,7 +172,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
name = "app-error"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"bincode",
@@ -192,7 +192,7 @@ dependencies = [
[[package]]
name = "appflowy-ai-client"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"bytes",
@@ -888,7 +888,7 @@ dependencies = [
[[package]]
name = "client-api"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"again",
"anyhow",
@@ -943,7 +943,7 @@ dependencies = [
[[package]]
name = "client-api-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"collab-entity",
"collab-rt-entity",
@@ -956,7 +956,7 @@ dependencies = [
[[package]]
name = "client-websocket"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"futures-channel",
"futures-util",
@@ -1030,7 +1030,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4175465382020f1bd9cd9aa0e0766178eac7b4e5#4175465382020f1bd9cd9aa0e0766178eac7b4e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d#699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d"
dependencies = [
"anyhow",
"arc-swap",
@@ -1055,7 +1055,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4175465382020f1bd9cd9aa0e0766178eac7b4e5#4175465382020f1bd9cd9aa0e0766178eac7b4e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d#699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d"
dependencies = [
"anyhow",
"async-trait",
@@ -1094,7 +1094,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4175465382020f1bd9cd9aa0e0766178eac7b4e5#4175465382020f1bd9cd9aa0e0766178eac7b4e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d#699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d"
dependencies = [
"anyhow",
"arc-swap",
@@ -1115,7 +1115,7 @@ dependencies = [
[[package]]
name = "collab-entity"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4175465382020f1bd9cd9aa0e0766178eac7b4e5#4175465382020f1bd9cd9aa0e0766178eac7b4e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d#699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d"
dependencies = [
"anyhow",
"bytes",
@@ -1135,7 +1135,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4175465382020f1bd9cd9aa0e0766178eac7b4e5#4175465382020f1bd9cd9aa0e0766178eac7b4e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d#699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d"
dependencies = [
"anyhow",
"arc-swap",
@@ -1157,7 +1157,7 @@ dependencies = [
[[package]]
name = "collab-importer"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4175465382020f1bd9cd9aa0e0766178eac7b4e5#4175465382020f1bd9cd9aa0e0766178eac7b4e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d#699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d"
dependencies = [
"anyhow",
"async-recursion",
@@ -1218,7 +1218,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4175465382020f1bd9cd9aa0e0766178eac7b4e5#4175465382020f1bd9cd9aa0e0766178eac7b4e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d#699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d"
dependencies = [
"anyhow",
"async-stream",
@@ -1256,7 +1256,7 @@ dependencies = [
[[package]]
name = "collab-rt-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"bincode",
@@ -1281,7 +1281,7 @@ dependencies = [
[[package]]
name = "collab-rt-protocol"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"async-trait",
@@ -1298,7 +1298,7 @@ dependencies = [
[[package]]
name = "collab-user"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4175465382020f1bd9cd9aa0e0766178eac7b4e5#4175465382020f1bd9cd9aa0e0766178eac7b4e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d#699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d"
dependencies = [
"anyhow",
"collab",
@@ -1678,7 +1678,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]]
name = "database-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"app-error",
@@ -3265,7 +3265,7 @@ dependencies = [
[[package]]
name = "gotrue"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"futures-util",
@@ -3282,7 +3282,7 @@ dependencies = [
[[package]]
name = "gotrue-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"app-error",
@@ -3714,7 +3714,7 @@ dependencies = [
[[package]]
name = "infra"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"bytes",
@@ -6398,7 +6398,7 @@ dependencies = [
[[package]]
name = "shared-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"app-error",
+8 -8
View File
@@ -120,14 +120,14 @@ custom-protocol = ["tauri/custom-protocol"]
# To switch to the local path, run:
# scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4175465382020f1bd9cd9aa0e0766178eac7b4e5" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4175465382020f1bd9cd9aa0e0766178eac7b4e5" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4175465382020f1bd9cd9aa0e0766178eac7b4e5" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4175465382020f1bd9cd9aa0e0766178eac7b4e5" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4175465382020f1bd9cd9aa0e0766178eac7b4e5" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4175465382020f1bd9cd9aa0e0766178eac7b4e5" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4175465382020f1bd9cd9aa0e0766178eac7b4e5" }
collab-importer = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4175465382020f1bd9cd9aa0e0766178eac7b4e5" }
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d" }
collab-importer = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d" }
# Working directory: frontend
# To update the commit ID, run:
+20 -20
View File
@@ -163,7 +163,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
name = "app-error"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"bincode",
@@ -183,7 +183,7 @@ dependencies = [
[[package]]
name = "appflowy-ai-client"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"bytes",
@@ -877,7 +877,7 @@ dependencies = [
[[package]]
name = "client-api"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"again",
"anyhow",
@@ -932,7 +932,7 @@ dependencies = [
[[package]]
name = "client-api-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"collab-entity",
"collab-rt-entity",
@@ -945,7 +945,7 @@ dependencies = [
[[package]]
name = "client-websocket"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"futures-channel",
"futures-util",
@@ -1028,7 +1028,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4175465382020f1bd9cd9aa0e0766178eac7b4e5#4175465382020f1bd9cd9aa0e0766178eac7b4e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d#699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d"
dependencies = [
"anyhow",
"arc-swap",
@@ -1053,7 +1053,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4175465382020f1bd9cd9aa0e0766178eac7b4e5#4175465382020f1bd9cd9aa0e0766178eac7b4e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d#699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d"
dependencies = [
"anyhow",
"async-trait",
@@ -1092,7 +1092,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4175465382020f1bd9cd9aa0e0766178eac7b4e5#4175465382020f1bd9cd9aa0e0766178eac7b4e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d#699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d"
dependencies = [
"anyhow",
"arc-swap",
@@ -1113,7 +1113,7 @@ dependencies = [
[[package]]
name = "collab-entity"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4175465382020f1bd9cd9aa0e0766178eac7b4e5#4175465382020f1bd9cd9aa0e0766178eac7b4e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d#699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d"
dependencies = [
"anyhow",
"bytes",
@@ -1133,7 +1133,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4175465382020f1bd9cd9aa0e0766178eac7b4e5#4175465382020f1bd9cd9aa0e0766178eac7b4e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d#699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d"
dependencies = [
"anyhow",
"arc-swap",
@@ -1155,7 +1155,7 @@ dependencies = [
[[package]]
name = "collab-importer"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4175465382020f1bd9cd9aa0e0766178eac7b4e5#4175465382020f1bd9cd9aa0e0766178eac7b4e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d#699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d"
dependencies = [
"anyhow",
"async-recursion",
@@ -1216,7 +1216,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4175465382020f1bd9cd9aa0e0766178eac7b4e5#4175465382020f1bd9cd9aa0e0766178eac7b4e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d#699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d"
dependencies = [
"anyhow",
"async-stream",
@@ -1254,7 +1254,7 @@ dependencies = [
[[package]]
name = "collab-rt-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"bincode",
@@ -1279,7 +1279,7 @@ dependencies = [
[[package]]
name = "collab-rt-protocol"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"async-trait",
@@ -1296,7 +1296,7 @@ dependencies = [
[[package]]
name = "collab-user"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4175465382020f1bd9cd9aa0e0766178eac7b4e5#4175465382020f1bd9cd9aa0e0766178eac7b4e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d#699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d"
dependencies = [
"anyhow",
"collab",
@@ -1683,7 +1683,7 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
[[package]]
name = "database-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"app-error",
@@ -3347,7 +3347,7 @@ dependencies = [
[[package]]
name = "gotrue"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"futures-util",
@@ -3364,7 +3364,7 @@ dependencies = [
[[package]]
name = "gotrue-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"app-error",
@@ -3801,7 +3801,7 @@ dependencies = [
[[package]]
name = "infra"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"bytes",
@@ -6478,7 +6478,7 @@ dependencies = [
[[package]]
name = "shared-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"app-error",
@@ -118,14 +118,14 @@ custom-protocol = ["tauri/custom-protocol"]
# To switch to the local path, run:
# scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4175465382020f1bd9cd9aa0e0766178eac7b4e5" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4175465382020f1bd9cd9aa0e0766178eac7b4e5" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4175465382020f1bd9cd9aa0e0766178eac7b4e5" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4175465382020f1bd9cd9aa0e0766178eac7b4e5" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4175465382020f1bd9cd9aa0e0766178eac7b4e5" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4175465382020f1bd9cd9aa0e0766178eac7b4e5" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4175465382020f1bd9cd9aa0e0766178eac7b4e5" }
collab-importer = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4175465382020f1bd9cd9aa0e0766178eac7b4e5" }
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d" }
collab-importer = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d" }
# Working directory: frontend
@@ -90,11 +90,11 @@ function AcceptInvitationPage () {
>
{t('invitation.join')}
{' '}
<span className={'font-semibold'}>{currentUser?.name}</span>
<span className={'font-semibold'}>{invitation?.workspace_name}</span>
{' '}
{t('invitation.on')}
{' '}
<span className={'whitespace-nowrap'}>{invitation?.workspace_name}</span>
<span className={'whitespace-nowrap'}>AppFlowy</span>
</div>
<Divider className={'max-w-full w-[400px]'} />
+8 -5
View File
@@ -445,7 +445,7 @@
"removeHomepage": "Remove homepage",
"selectHomePage": "Select a page",
"clearHomePage": "Clear the home page for this namespace",
"customUrl": "Custom url",
"customUrl": "Custom URL",
"namespace": {
"description": "This change will apply to all the published pages live on this namespace",
"tooltip": "We reserve the rights to remove any inappropriate namespaces",
@@ -935,9 +935,11 @@
"itemFive": "Mobile app",
"itemSix": "AI Responses",
"itemFileUpload": "File uploads",
"customNamespace": "Custom namespace",
"tooltipSix": "Lifetime means the number of responses never reset",
"intelligentSearch": "Intelligent search",
"tooltipSeven": "Allows you to customize part of the URL for your workspace"
"tooltipSeven": "Allows you to customize part of the URL for your workspace",
"customNamespaceTooltip": "Custom published site URL"
},
"freeLabels": {
"itemOne": "Charged per workspace",
@@ -1648,14 +1650,15 @@
"file": "File"
},
"subPage": {
"name": "Insert page",
"name": "Document",
"keyword1": "sub page",
"keyword2": "page",
"keyword3": "child page",
"keyword4": "insert page",
"keyword5": "embed page",
"keyword6": "new page",
"keyword7": "create page"
"keyword7": "create page",
"keyword8": "document"
}
},
"selectionMenu": {
@@ -2825,4 +2828,4 @@
"many": "{count} members",
"other": "{count} members"
}
}
}
+20 -20
View File
@@ -163,7 +163,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
name = "app-error"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"bincode",
@@ -183,7 +183,7 @@ dependencies = [
[[package]]
name = "appflowy-ai-client"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"bytes",
@@ -780,7 +780,7 @@ dependencies = [
[[package]]
name = "client-api"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"again",
"anyhow",
@@ -835,7 +835,7 @@ dependencies = [
[[package]]
name = "client-api-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"collab-entity",
"collab-rt-entity",
@@ -848,7 +848,7 @@ dependencies = [
[[package]]
name = "client-websocket"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"futures-channel",
"futures-util",
@@ -891,7 +891,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4175465382020f1bd9cd9aa0e0766178eac7b4e5#4175465382020f1bd9cd9aa0e0766178eac7b4e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d#699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d"
dependencies = [
"anyhow",
"arc-swap",
@@ -916,7 +916,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4175465382020f1bd9cd9aa0e0766178eac7b4e5#4175465382020f1bd9cd9aa0e0766178eac7b4e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d#699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d"
dependencies = [
"anyhow",
"async-trait",
@@ -955,7 +955,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4175465382020f1bd9cd9aa0e0766178eac7b4e5#4175465382020f1bd9cd9aa0e0766178eac7b4e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d#699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d"
dependencies = [
"anyhow",
"arc-swap",
@@ -976,7 +976,7 @@ dependencies = [
[[package]]
name = "collab-entity"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4175465382020f1bd9cd9aa0e0766178eac7b4e5#4175465382020f1bd9cd9aa0e0766178eac7b4e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d#699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d"
dependencies = [
"anyhow",
"bytes",
@@ -996,7 +996,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4175465382020f1bd9cd9aa0e0766178eac7b4e5#4175465382020f1bd9cd9aa0e0766178eac7b4e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d#699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d"
dependencies = [
"anyhow",
"arc-swap",
@@ -1018,7 +1018,7 @@ dependencies = [
[[package]]
name = "collab-importer"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4175465382020f1bd9cd9aa0e0766178eac7b4e5#4175465382020f1bd9cd9aa0e0766178eac7b4e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d#699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d"
dependencies = [
"anyhow",
"async-recursion",
@@ -1079,7 +1079,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4175465382020f1bd9cd9aa0e0766178eac7b4e5#4175465382020f1bd9cd9aa0e0766178eac7b4e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d#699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d"
dependencies = [
"anyhow",
"async-stream",
@@ -1117,7 +1117,7 @@ dependencies = [
[[package]]
name = "collab-rt-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"bincode",
@@ -1142,7 +1142,7 @@ dependencies = [
[[package]]
name = "collab-rt-protocol"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"async-trait",
@@ -1159,7 +1159,7 @@ dependencies = [
[[package]]
name = "collab-user"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4175465382020f1bd9cd9aa0e0766178eac7b4e5#4175465382020f1bd9cd9aa0e0766178eac7b4e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d#699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d"
dependencies = [
"anyhow",
"collab",
@@ -1537,7 +1537,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]]
name = "database-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"app-error",
@@ -2979,7 +2979,7 @@ dependencies = [
[[package]]
name = "gotrue"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"futures-util",
@@ -2996,7 +2996,7 @@ dependencies = [
[[package]]
name = "gotrue-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"app-error",
@@ -3358,7 +3358,7 @@ dependencies = [
[[package]]
name = "infra"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"bytes",
@@ -5663,7 +5663,7 @@ dependencies = [
[[package]]
name = "shared-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4643e568d2d9b725b0d06dfbbf351317cf9a2c9f#4643e568d2d9b725b0d06dfbbf351317cf9a2c9f"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a"
dependencies = [
"anyhow",
"app-error",
+10 -10
View File
@@ -106,8 +106,8 @@ dashmap = "6.0.1"
# Run the script.add_workspace_members:
# scripts/tool/update_client_api_rev.sh new_rev_id
# ⚠️⚠️⚠️️
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "4643e568d2d9b725b0d06dfbbf351317cf9a2c9f" }
client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "4643e568d2d9b725b0d06dfbbf351317cf9a2c9f" }
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a" }
client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a" }
[profile.dev]
opt-level = 0
@@ -142,14 +142,14 @@ rocksdb = { git = "https://github.com/rust-rocksdb/rust-rocksdb", rev = "1710120
# To switch to the local path, run:
# scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4175465382020f1bd9cd9aa0e0766178eac7b4e5" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4175465382020f1bd9cd9aa0e0766178eac7b4e5" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4175465382020f1bd9cd9aa0e0766178eac7b4e5" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4175465382020f1bd9cd9aa0e0766178eac7b4e5" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4175465382020f1bd9cd9aa0e0766178eac7b4e5" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4175465382020f1bd9cd9aa0e0766178eac7b4e5" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4175465382020f1bd9cd9aa0e0766178eac7b4e5" }
collab-importer = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4175465382020f1bd9cd9aa0e0766178eac7b4e5" }
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d" }
collab-importer = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "699d6e3b45b9ac23987c6eb5fc3df3e29f3cbf9d" }
# Working directory: frontend
# To update the commit ID, run:
@@ -411,7 +411,7 @@ impl LocalAIResourceController {
Ok(())
}
#[instrument(level = "info", skip_all, err)]
#[instrument(level = "info", skip_all)]
pub fn get_chat_config(&self, rag_enabled: bool) -> FlowyResult<AIPluginConfig> {
if !self.is_resource_ready() {
return Err(FlowyError::local_ai().with_context("Local AI resources are not ready"));
+2 -2
View File
@@ -29,8 +29,8 @@ collab-entity = { workspace = true }
collab-plugins = { workspace = true }
collab-folder = { workspace = true }
#collab = { workspace = true }
collab = { workspace = true, features = ["verbose_log"] }
collab = { workspace = true }
#collab = { workspace = true, features = ["verbose_log"] }
diesel.workspace = true
uuid.workspace = true
@@ -348,7 +348,7 @@ impl FolderCloudService for ServerProvider {
async fn set_publish_namespace(
&self,
workspace_id: &str,
new_namespace: &str,
new_namespace: String,
) -> Result<(), FlowyError> {
let server = self.get_server()?;
server
@@ -72,7 +72,7 @@ pub trait FolderCloudService: Send + Sync + 'static {
async fn set_publish_namespace(
&self,
workspace_id: &str,
new_namespace: &str,
new_namespace: String,
) -> Result<(), FlowyError>;
async fn list_published_views(
@@ -81,7 +81,7 @@ impl From<PublishInfo> for PublishInfoResponsePB {
Self {
view_id: info.view_id.to_string(),
publish_name: info.publish_name,
namespace: info.namespace,
namespace: Some(info.namespace),
publisher_email: info.publisher_email,
publish_timestamp_sec: info.publish_timestamp.timestamp(),
}
@@ -1298,11 +1298,11 @@ impl FolderManager {
/// Get the namespace of the current workspace.
/// The namespace is used to generate the URL of the published view.
#[tracing::instrument(level = "debug", skip(self), err)]
pub async fn set_publish_namespace(&self, namespace: String) -> FlowyResult<()> {
pub async fn set_publish_namespace(&self, new_namespace: String) -> FlowyResult<()> {
let workspace_id = self.user.workspace_id()?;
self
.cloud_service
.set_publish_namespace(workspace_id.as_str(), namespace.as_str())
.set_publish_namespace(workspace_id.as_str(), new_namespace)
.await?;
Ok(())
}
@@ -268,13 +268,12 @@ where
async fn set_publish_namespace(
&self,
workspace_id: &str,
new_namespace: &str,
new_namespace: String,
) -> Result<(), FlowyError> {
let workspace_id = workspace_id.to_string();
let namespace = new_namespace.to_string();
let try_get_client = self.inner.try_get_client();
try_get_client?
.set_workspace_publish_namespace(&workspace_id, &namespace)
.set_workspace_publish_namespace(&workspace_id, new_namespace)
.await?;
Ok(())
}
@@ -98,7 +98,7 @@ impl FolderCloudService for LocalServerFolderCloudServiceImpl {
async fn set_publish_namespace(
&self,
_workspace_id: &str,
_new_namespace: &str,
_new_namespace: String,
) -> Result<(), FlowyError> {
Err(FlowyError::local_version_not_support())
}
@@ -7,7 +7,6 @@ use serde::Serialize;
use std::fmt::Display;
use std::ops::{Deref, DerefMut};
use tokio::sync::broadcast;
use tracing::warn;
#[async_trait]
pub trait StorageService: Send + Sync {
@@ -68,22 +67,25 @@ pub enum FileUploadState {
#[derive(Clone, Debug, Serialize)]
pub struct FileProgress {
pub file_url: String,
pub file_id: String,
pub progress: f64,
pub error: Option<String>,
}
impl FileProgress {
pub fn new_progress(file_url: String, progress: f64) -> Self {
pub fn new_progress(file_url: String, file_id: String, progress: f64) -> Self {
FileProgress {
file_url,
progress,
file_id,
progress: (progress * 10.0).round() / 10.0,
error: None,
}
}
pub fn new_error(file_url: String, error: String) -> Self {
pub fn new_error(file_url: String, file_id: String, error: String) -> Self {
FileProgress {
file_url,
file_id,
progress: 0.0,
error: Some(error),
}
@@ -92,7 +94,7 @@ impl FileProgress {
impl Display for FileProgress {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "FileProgress: {} - {}", self.file_url, self.progress)
write!(f, "FileProgress: {} - {}", self.file_id, self.progress)
}
}
@@ -122,9 +124,7 @@ impl ProgressNotifier {
pub async fn notify(&mut self, progress: FileUploadState) {
self.current_value = Some(progress.clone());
if let Err(err) = self.tx.send(progress) {
warn!("Failed to send progress notification: {:?}", err);
}
let _ = self.tx.send(progress);
}
}
+142 -122
View File
@@ -2,9 +2,9 @@ use crate::entities::FileStatePB;
use crate::file_cache::FileTempStorage;
use crate::notification::{make_notification, StorageNotification};
use crate::sqlite_sql::{
batch_select_upload_file, delete_upload_file, insert_upload_file, insert_upload_part,
is_upload_completed, select_upload_file, select_upload_parts, update_upload_file_completed,
update_upload_file_upload_id, UploadFilePartTable, UploadFileTable,
batch_select_upload_file, delete_all_upload_parts, delete_upload_file, insert_upload_file,
insert_upload_part, is_upload_completed, select_upload_file, select_upload_parts,
update_upload_file_completed, update_upload_file_upload_id, UploadFilePartTable, UploadFileTable,
};
use crate::uploader::{FileUploader, FileUploaderRunner, Signal, UploadTask, UploadTaskQueue};
use allo_isolate::Isolate;
@@ -64,7 +64,7 @@ impl StorageManager {
"{}/cache_files",
user_service.get_application_root_dir()
));
let (global_notifier, _) = broadcast::channel(1000);
let (global_notifier, _) = broadcast::channel(2000);
let temp_storage = Arc::new(FileTempStorage::new(temp_storage_path));
let (notifier, notifier_rx) = watch::channel(Signal::Proceed);
let task_queue = Arc::new(UploadTaskQueue::new(notifier));
@@ -101,6 +101,31 @@ impl StorageManager {
}
});
let mut rx = global_notifier.subscribe();
let weak_notifier = Arc::downgrade(&progress_notifiers);
tokio::spawn(async move {
while let Ok(progress) = rx.recv().await {
if let Some(notifiers) = weak_notifier.upgrade() {
if let Some(mut notifier) = notifiers.get_mut(&progress.file_id) {
if progress.progress >= 1.0 {
let finish = FileUploadState::Finished {
file_id: progress.file_id,
};
notifier.notify(finish).await;
} else {
let progress = FileUploadState::Uploading {
progress: progress.progress,
};
notifier.notify(progress).await;
}
}
} else {
info!("progress notifiers is dropped");
break;
}
}
});
Self {
storage_service,
cloud_service,
@@ -139,6 +164,7 @@ impl StorageManager {
if let Err(err) = self.global_notifier.send(FileProgress::new_progress(
url.to_string(),
file_id.clone(),
if is_finish { 1.0 } else { 0.0 },
)) {
error!("[File] send global notifier failed: {}", err);
@@ -194,7 +220,7 @@ async fn prepare_upload_task(
) -> FlowyResult<()> {
let uid = user_service.user_id()?;
let conn = user_service.sqlite_connection(uid)?;
let upload_files = batch_select_upload_file(conn, 100)?;
let upload_files = batch_select_upload_file(conn, 100, false)?;
let tasks = upload_files
.into_iter()
.map(|upload_file| UploadTask::BackgroundTask {
@@ -205,7 +231,7 @@ async fn prepare_upload_task(
retry_count: 0,
})
.collect::<Vec<_>>();
info!("prepare upload task: {}", tasks.len());
info!("[File] prepare upload task: {}", tasks.len());
uploader.queue_tasks(tasks).await;
Ok(())
}
@@ -317,7 +343,6 @@ impl StorageService for StorageServiceImpl {
// 1. create a file record and chunk the file
let (chunks, record) = create_upload_record(workspace_id, parent_dir, local_file_path).await?;
// 2. save the record to sqlite
let conn = self
.user_service
@@ -359,7 +384,7 @@ impl StorageService for StorageServiceImpl {
},
Err(err) => {
if matches!(err.code, ErrorCode::DuplicateSqliteRecord) {
info!("upload record already exists, skip creating new upload task");
info!("[File] upload record already exists, skip creating new upload task");
Ok::<_, FlowyError>((CreatedUpload { url, file_id }, None))
} else {
Err(err)
@@ -373,19 +398,16 @@ impl StorageService for StorageServiceImpl {
FlowyError::internal().with_context("failed to downcast record to UploadFileTable")
})?;
if let Err(err) = start_upload(
start_upload(
&self.cloud_service,
&self.user_service,
&self.temp_storage,
chunks,
file_record,
self.progress_notifiers.clone(),
self.global_notifier.clone(),
)
.await
{
error!("[File] start upload failed: {}", err);
}
.await?;
Ok(())
}
@@ -406,7 +428,6 @@ impl StorageService for StorageServiceImpl {
&self.user_service,
&self.temp_storage,
upload_file,
self.progress_notifiers.clone(),
self.global_notifier.clone(),
)
.await?;
@@ -462,6 +483,7 @@ async fn create_upload_record(
let record = UploadFileTable {
workspace_id,
file_id,
// When the upload_id is empty string, we will create a new upload using [Self::start_upload] method
upload_id: "".to_string(),
parent_dir,
local_file_path,
@@ -481,7 +503,6 @@ async fn start_upload(
temp_storage: &Arc<FileTempStorage>,
mut chunked_bytes: ChunkedBytes,
upload_file: &UploadFileTable,
progress_notifiers: Arc<DashMap<String, ProgressNotifier>>,
global_notifier: GlobalNotifier,
) -> FlowyResult<()> {
// 4. gather existing completed parts
@@ -496,6 +517,7 @@ async fn start_upload(
.collect::<Vec<_>>();
let upload_offset = completed_parts.len() as i32;
let total_parts = chunked_bytes.iter().count();
chunked_bytes.set_current_offset(upload_offset);
info!(
@@ -504,68 +526,72 @@ async fn start_upload(
);
let mut upload_file = upload_file.clone();
if upload_file.upload_id.is_empty() {
// 1. create upload
trace!(
"[File] create upload for workspace: {}, parent_dir: {}, file_id: {}",
upload_file.workspace_id,
upload_file.parent_dir,
upload_file.file_id
);
// 1. create upload
trace!(
"[File] create upload for workspace: {}, parent_dir: {}, file_id: {}",
upload_file.workspace_id,
upload_file.parent_dir,
upload_file.file_id
);
let create_upload_resp_result = cloud_service
.create_upload(
&upload_file.workspace_id,
&upload_file.parent_dir,
&upload_file.file_id,
&upload_file.content_type,
)
.await;
if let Err(err) = create_upload_resp_result.as_ref() {
if err.is_file_limit_exceeded() {
make_notification(StorageNotification::FileStorageLimitExceeded)
.payload(err.clone())
.send();
}
}
let create_upload_resp = create_upload_resp_result?;
// 2. update upload_id
let conn = user_service.sqlite_connection(user_service.user_id()?)?;
update_upload_file_upload_id(
conn,
let create_upload_resp_result = cloud_service
.create_upload(
&upload_file.workspace_id,
&upload_file.parent_dir,
&upload_file.file_id,
&create_upload_resp.upload_id,
)?;
trace!(
"[File] {} update upload_id: {}",
upload_file.file_id,
create_upload_resp.upload_id
);
// temporary store the upload_id
upload_file.upload_id = create_upload_resp.upload_id;
&upload_file.content_type,
)
.await;
if let Err(err) = create_upload_resp_result.as_ref() {
if err.is_file_limit_exceeded() {
make_notification(StorageNotification::FileStorageLimitExceeded)
.payload(err.clone())
.send();
}
}
let create_upload_resp = create_upload_resp_result?;
// 2. update upload_id
let conn = user_service.sqlite_connection(user_service.user_id()?)?;
update_upload_file_upload_id(
conn,
&upload_file.workspace_id,
&upload_file.parent_dir,
&upload_file.file_id,
&create_upload_resp.upload_id,
)?;
trace!(
"[File] {} update upload_id: {}",
upload_file.file_id,
create_upload_resp.upload_id
);
upload_file.upload_id = create_upload_resp.upload_id;
// 3. start uploading parts
trace!(
"[File] {} start uploading parts: {}",
info!(
"[File] {} start uploading parts:{}, offset:{}",
upload_file.file_id,
chunked_bytes.iter().count()
chunked_bytes.iter().count(),
upload_offset,
);
let total_parts = chunked_bytes.iter().count();
let iter = chunked_bytes.iter().enumerate();
for (index, chunk_bytes) in iter {
let part_number = upload_offset + index as i32 + 1;
trace!(
info!(
"[File] {} uploading {}th part, size:{}KB",
upload_file.file_id,
part_number,
chunk_bytes.len() / 1000,
);
let file_url = cloud_service
.get_object_url_v1(
&upload_file.workspace_id,
&upload_file.parent_dir,
&upload_file.file_id,
)
.await?;
// start uploading parts
match upload_part(
cloud_service,
@@ -580,29 +606,17 @@ async fn start_upload(
.await
{
Ok(resp) => {
let progress = (part_number as f64 / total_parts as f64).clamp(0.0, 1.0);
trace!(
"[File] {} upload progress: {}",
upload_file.file_id,
progress
);
let file_url = cloud_service
.get_object_url_v1(
&upload_file.workspace_id,
&upload_file.parent_dir,
&upload_file.file_id,
)
.await?;
if let Err(err) = global_notifier.send(FileProgress::new_progress(file_url, progress)) {
error!("[File] send global notifier failed: {}", err);
let mut progress_value = (part_number as f64 / total_parts as f64).clamp(0.0, 1.0);
// The 0.1 is reserved for the complete_upload progress
if progress_value >= 0.9 {
progress_value = 0.9;
}
let progress =
FileProgress::new_progress(file_url, upload_file.file_id.clone(), progress_value);
trace!("[File] upload progress: {}", progress);
if let Some(mut notifier) = progress_notifiers.get_mut(&upload_file.file_id) {
notifier
.notify(FileUploadState::Uploading { progress })
.await;
if let Err(err) = global_notifier.send(progress) {
error!("[File] send global notifier failed: {}", err);
}
// gather completed part
@@ -618,7 +632,13 @@ async fn start_upload(
.send();
}
error!("[File] {} upload part failed: {}", upload_file.file_id, err);
if let Err(err) = global_notifier.send(FileProgress::new_error(
file_url,
upload_file.file_id.clone(),
err.msg.clone(),
)) {
error!("[File] send global notifier failed: {}", err);
}
return Err(err);
},
}
@@ -631,7 +651,6 @@ async fn start_upload(
temp_storage,
&upload_file,
completed_parts,
&progress_notifiers,
&global_notifier,
)
.await;
@@ -641,9 +660,10 @@ async fn start_upload(
.payload(err.clone())
.send();
}
return Err(err);
}
trace!("[File] {} upload completed", upload_file.file_id);
Ok(())
}
@@ -653,7 +673,6 @@ async fn resume_upload(
user_service: &Arc<dyn StorageUserService>,
temp_storage: &Arc<FileTempStorage>,
upload_file: UploadFileTable,
progress_notifiers: Arc<DashMap<String, ProgressNotifier>>,
global_notifier: GlobalNotifier,
) -> FlowyResult<()> {
trace!(
@@ -673,26 +692,22 @@ async fn resume_upload(
temp_storage,
chunked_bytes,
&upload_file,
progress_notifiers,
global_notifier,
)
.await?;
},
Err(err) => {
//
match err.kind() {
ErrorKind::NotFound => {
error!("[File] file not found: {}", upload_file.local_file_path);
if let Ok(uid) = user_service.user_id() {
if let Ok(conn) = user_service.sqlite_connection(uid) {
delete_upload_file(conn, &upload_file.upload_id)?;
}
Err(err) => match err.kind() {
ErrorKind::NotFound => {
error!("[File] file not found: {}", upload_file.local_file_path);
if let Ok(uid) = user_service.user_id() {
if let Ok(conn) = user_service.sqlite_connection(uid) {
delete_upload_file(conn, &upload_file.upload_id)?;
}
},
_ => {
error!("[File] read file failed: {}", err);
},
}
}
},
_ => {
error!("[File] read file failed: {}", err);
},
},
}
Ok(())
@@ -741,13 +756,21 @@ async fn complete_upload(
temp_storage: &Arc<FileTempStorage>,
upload_file: &UploadFileTable,
parts: Vec<CompletedPartRequest>,
progress_notifiers: &Arc<DashMap<String, ProgressNotifier>>,
global_notifier: &GlobalNotifier,
) -> Result<(), FlowyError> {
trace!(
"[File]: completing file upload: {}, num parts: {}",
let file_url = cloud_service
.get_object_url_v1(
&upload_file.workspace_id,
&upload_file.parent_dir,
&upload_file.file_id,
)
.await?;
info!(
"[File]: completing file upload: {}, num parts: {}, url:{}",
upload_file.file_id,
parts.len()
parts.len(),
file_url
);
match cloud_service
.complete_upload(
@@ -761,27 +784,12 @@ async fn complete_upload(
{
Ok(_) => {
info!("[File] completed upload file: {}", upload_file.file_id);
if let Some(mut notifier) = progress_notifiers.get_mut(&upload_file.file_id) {
notifier
.notify(FileUploadState::Finished {
file_id: upload_file.file_id.clone(),
})
.await;
}
let file_url = cloud_service
.get_object_url_v1(
&upload_file.workspace_id,
&upload_file.parent_dir,
&upload_file.file_id,
)
.await?;
let progress = FileProgress::new_progress(file_url, 1.0);
let progress = FileProgress::new_progress(file_url, upload_file.file_id.clone(), 1.0);
info!(
"[File]: notify upload progress:{}, {}",
upload_file.file_id, progress
);
if let Err(err) = global_notifier.send(progress) {
error!("[File] send global notifier failed: {}", err);
}
@@ -797,6 +805,18 @@ async fn complete_upload(
},
Err(err) => {
error!("[File] complete upload failed: {}", err);
let progress =
FileProgress::new_error(file_url, upload_file.file_id.clone(), err.msg.clone());
if let Err(send_err) = global_notifier.send(progress) {
error!("[File] send global notifier failed: {}", send_err);
}
let conn = user_service.sqlite_connection(user_service.user_id()?)?;
if let Err(err) = delete_all_upload_parts(conn, &upload_file.upload_id) {
error!("[File] delete all upload parts failed: {}", err);
}
return Err(err);
},
}
Ok(())
@@ -137,6 +137,14 @@ pub fn delete_upload_file(mut conn: DBConnection, upload_id: &str) -> FlowyResul
Ok(())
}
pub fn delete_all_upload_parts(mut conn: DBConnection, upload_id: &str) -> FlowyResult<()> {
diesel::delete(
upload_file_part::dsl::upload_file_part.filter(upload_file_part::upload_id.eq(upload_id)),
)
.execute(&mut *conn)?;
Ok(())
}
pub fn insert_upload_part(
mut conn: DBConnection,
upload_part: &UploadFilePartTable,
@@ -172,11 +180,14 @@ pub fn select_upload_parts(
pub fn batch_select_upload_file(
mut conn: DBConnection,
limit: i32,
is_finish: bool,
) -> FlowyResult<Vec<UploadFileTable>> {
let results = upload_file_table::dsl::upload_file_table
.filter(upload_file_table::is_finish.eq(is_finish))
.order(upload_file_table::created_at.desc())
.limit(limit.into())
.load::<UploadFileTable>(&mut conn)?;
.load::<UploadFileTable>(&mut *conn)?;
Ok(results)
}
@@ -10,7 +10,7 @@ use std::sync::atomic::{AtomicBool, AtomicU8};
use std::sync::{Arc, Weak};
use std::time::Duration;
use tokio::sync::{watch, RwLock};
use tracing::{error, info, trace};
use tracing::{error, info, instrument, trace, warn};
#[derive(Clone)]
pub enum Signal {
@@ -34,7 +34,7 @@ impl UploadTaskQueue {
pub async fn queue_task(&self, task: UploadTask) {
trace!("[File] Queued task: {}", task);
self.tasks.write().await.push(task);
let _ = self.notifier.send(Signal::Proceed);
let _ = self.notifier.send_replace(Signal::Proceed);
}
}
@@ -104,6 +104,7 @@ impl FileUploader {
let _ = self.queue.notifier.send(Signal::ProceedAfterSecs(3));
}
#[instrument(name = "[File]: process next", level = "debug", skip(self))]
pub async fn process_next(&self) -> Option<()> {
// Do not proceed if the uploader is paused.
if self.pause_sync.load(std::sync::atomic::Ordering::Relaxed) {
@@ -125,6 +126,7 @@ impl FileUploader {
{
// If the current uploads count is greater than or equal to the max uploads, do not proceed.
let _ = self.queue.notifier.send(Signal::ProceedAfterSecs(10));
trace!("[File] max uploads reached, process_next after 10 seconds");
return None;
}
@@ -133,6 +135,7 @@ impl FileUploader {
.load(std::sync::atomic::Ordering::SeqCst)
{
// If the storage limitation is enabled, do not proceed.
error!("[File] storage limit exceeded, uploader is disabled");
return None;
}
@@ -140,6 +143,7 @@ impl FileUploader {
if task.retry_count() > 5 {
// If the task has been retried more than 5 times, we should not retry it anymore.
let _ = self.queue.notifier.send(Signal::ProceedAfterSecs(2));
warn!("[File] Task has been retried more than 5 times: {}", task);
return None;
}
@@ -166,12 +170,12 @@ impl FileUploader {
.await
{
if err.is_file_limit_exceeded() {
error!("Failed to upload file: {}", err);
error!("[File] Failed to upload file: {}", err);
self.disable_storage_write();
}
info!(
"Failed to upload file: {}, retry_count:{}",
"[File] Failed to upload file: {}, retry_count:{}",
err, retry_count
);
@@ -197,12 +201,12 @@ impl FileUploader {
.await
{
if err.is_file_limit_exceeded() {
error!("Failed to upload file: {}", err);
error!("[File] failed to upload file: {}", err);
self.disable_storage_write();
}
info!(
"Failed to resume upload file: {}, retry_count:{}",
"[File] failed to resume upload file: {}, retry_count:{}",
err, retry_count
);
retry_count += 1;
@@ -216,10 +220,15 @@ impl FileUploader {
}
},
}
self
.current_uploads
.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
let _ = self.queue.notifier.send(Signal::ProceedAfterSecs(2));
trace!("[File] process_next after 2 seconds");
self
.queue
.notifier
.send_replace(Signal::ProceedAfterSecs(2));
None
}
}
@@ -42,7 +42,7 @@ async fn test_insert_new_upload() {
// select
let conn = db.get_connection().unwrap();
let records = batch_select_upload_file(conn, 100).unwrap();
let records = batch_select_upload_file(conn, 100, false).unwrap();
assert_eq!(records.len(), 5);
// compare the upload id order is the same as upload_ids
@@ -55,7 +55,7 @@ async fn test_insert_new_upload() {
}
let conn = db.get_connection().unwrap();
let records = batch_select_upload_file(conn, 100).unwrap();
let records = batch_select_upload_file(conn, 100, false).unwrap();
assert!(records.is_empty());
}
@@ -73,8 +73,16 @@ impl UserManager {
let reminder = Reminder::from(reminder_pb);
self
.mut_awareness(|user_awareness| {
user_awareness.update_reminder(&reminder.id, |new_reminder| {
new_reminder.clone_from(&reminder)
user_awareness.update_reminder(&reminder.id, |update| {
update
.set_object_id(&reminder.object_id)
.set_title(&reminder.title)
.set_message(&reminder.message)
.set_is_ack(reminder.is_ack)
.set_is_read(reminder.is_read)
.set_scheduled_at(reminder.scheduled_at)
.set_type(reminder.ty)
.set_meta(reminder.meta.clone().into_inner());
});
})
.await?;