fix: paste issue with the nested image (#7857)

* chore: replace delete button style

* chore: replace textfield in settings page

* fix: icon text align issue in database

* fix: member count is 0

* fix: invite member issue on mobile

* fix: checklist item align issue on Windows

* fix: integration test

* fix: support converting paragraph to markdown

* chore: update editor version

* fix: delete command in simple table cell

* feat: use new password error code
This commit is contained in:
Lucas 2025-04-30 11:45:48 +08:00 committed by GitHub
parent 64dff44ce1
commit 63244e0b3c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 146 additions and 127 deletions

View File

@ -9,7 +9,6 @@ import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu_element.dart';
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/text_field.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
@ -86,7 +85,7 @@ extension AppFlowySettings on WidgetTester {
final userNameFinder = find.descendant(
of: find.byType(AccountUserProfile),
matching: find.byType(FlowyTextField),
matching: find.byType(TextField),
);
await enterText(userNameFinder, name);
await pumpAndSettle();

View File

@ -200,7 +200,9 @@ class MobileViewPage extends StatelessWidget {
const WidgetSpan(child: HSpace(8.0)),
],
TextSpan(
text: name,
text: name.orDefault(
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
),
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 16.0,
fontWeight: FontWeight.w600,

View File

@ -108,31 +108,33 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
child: BlocConsumer<WorkspaceMemberBloc, WorkspaceMemberState>(
listener: _onListener,
builder: (context, state) {
return Column(
children: [
if (state.myRole.isOwner) ...[
Container(
width: double.infinity,
padding: EdgeInsets.all(theme.spacing.xl),
child: const MInviteMemberByLink(),
),
VSpace(theme.spacing.m),
return SingleChildScrollView(
child: Column(
children: [
if (state.myRole.isOwner) ...[
Container(
width: double.infinity,
padding: EdgeInsets.all(theme.spacing.xl),
child: const MInviteMemberByLink(),
),
VSpace(theme.spacing.m),
],
if (state.members.isNotEmpty) ...[
const AFDivider(),
VSpace(theme.spacing.xl),
MobileMemberList(
members: state.members,
userProfile: userProfile,
myRole: state.myRole,
),
],
if (state.myRole.isMember) ...[
Spacer(),
const _LeaveWorkspaceButton(),
],
const VSpace(48),
],
if (state.members.isNotEmpty) ...[
const AFDivider(),
VSpace(theme.spacing.xl),
MobileMemberList(
members: state.members,
userProfile: userProfile,
myRole: state.myRole,
),
],
if (state.myRole.isMember) ...[
Spacer(),
const _LeaveWorkspaceButton(),
],
const VSpace(48),
],
),
);
},
),

View File

@ -1,6 +1,7 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/setting/widgets/mobile_setting_trailing.dart';
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -18,14 +19,22 @@ class WorkspaceSettingGroup extends StatelessWidget {
Widget build(BuildContext context) {
return BlocBuilder<UserWorkspaceBloc, UserWorkspaceState>(
builder: (context, state) {
final currentWorkspace = state.currentWorkspace;
final currentWorkspace = state.workspaces.firstWhereOrNull(
(e) => e.workspaceId == state.currentWorkspace?.workspaceId,
);
final memberCount = currentWorkspace?.memberCount;
String memberCountText = '';
// if the member count is greater than 0, show the member count
if (memberCount != null && memberCount > 0) {
memberCountText = memberCount.toString();
}
return MobileSettingGroup(
groupTitle: LocaleKeys.settings_appearance_members_label.tr(),
settingItemList: [
MobileSettingItem(
name: LocaleKeys.settings_appearance_members_label.tr(),
trailing: MobileSettingTrailing(
text: currentWorkspace?.memberCount.toString() ?? '',
text: memberCountText,
),
onTap: () {
context.push(InviteMembersScreen.routeName);

View File

@ -280,6 +280,7 @@ class _ChecklistItemState extends State<ChecklistItem> {
child: ChecklistCellTextfield(
textController: textController,
focusNode: textFieldFocusNode,
lineHeight: Platform.isWindows ? 1.2 : 1.1,
onChanged: () {
_debounceOnChanged.call(() {
if (!isComposing) {

View File

@ -46,6 +46,7 @@ class ChecklistCellTextfield extends StatelessWidget {
horizontal: 2,
),
this.onSubmitted,
this.lineHeight,
});
final TextEditingController textController;
@ -53,13 +54,17 @@ class ChecklistCellTextfield extends StatelessWidget {
final EdgeInsetsGeometry contentPadding;
final VoidCallback? onSubmitted;
final VoidCallback? onChanged;
final double? lineHeight;
@override
Widget build(BuildContext context) {
final textStyle = Theme.of(context).textTheme.bodyMedium;
return TextField(
controller: textController,
focusNode: focusNode,
style: Theme.of(context).textTheme.bodyMedium,
style: textStyle?.copyWith(
height: lineHeight,
),
maxLines: null,
decoration: InputDecoration(
border: InputBorder.none,

View File

@ -31,15 +31,22 @@ Future<bool> _convertBacktickToCodeBlock({
}
// only active when the backtick is at the beginning of the line
final keyword = '``';
final plainText = delta.toPlainText();
if (plainText != '``') {
if (!plainText.startsWith(keyword)) {
return false;
}
final transaction = editorState.transaction;
transaction.insertNode(
final deltaWithoutKeyword = delta.compose(Delta()..delete(keyword.length));
transaction.insertNodes(
selection.end.path,
codeBlockNode(),
[
codeBlockNode(
delta: deltaWithoutKeyword,
),
if (node.children.isNotEmpty) ...node.children.map((e) => e.copyWith()),
],
);
transaction.deleteNode(node);
transaction.afterSelection = Selection.collapsed(

View File

@ -1,4 +1,5 @@
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_block_component.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_cell_block_component.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
@ -96,7 +97,13 @@ CommandShortcutEventHandler _deleteInNotCollapsedSelection = (editorState) {
if (selection == null || selection.isCollapsed) {
return KeyEventResult.ignored;
}
editorState.deleteSelection(selection);
editorState.deleteSelection(
selection,
ignoreNodeTypes: [
SimpleTableCellBlockKeys.type,
TableCellBlockKeys.type,
],
);
return KeyEventResult.handled;
};

View File

@ -1,6 +1,7 @@
import 'dart:convert';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_result/appflowy_result.dart';
import 'package:http/http.dart' as http;
@ -207,8 +208,15 @@ class PasswordHttpService {
'${endpoint.name} request failed: ${response.statusCode}, $errorBody ',
);
ErrorCode errorCode = ErrorCode.Internal;
if (response.statusCode == 422) {
errorCode = ErrorCode.NewPasswordTooWeak;
}
return FlowyResult.failure(
FlowyError(
code: errorCode,
msg: errorBody['msg'] ?? errorMessage,
),
);

View File

@ -5,6 +5,7 @@ import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/contin
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/continue_with/title_logo.dart';
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/continue_with/verifying_button.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.dart';
import 'package:appflowy_ui/appflowy_ui.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
@ -60,9 +61,15 @@ class _SetNewPasswordWidgetState extends State<SetNewPasswordWidget> {
});
},
(error) {
newPasswordKey.currentState?.syncError(
errorText: error.msg,
);
if (error.code == ErrorCode.NewPasswordTooWeak) {
newPasswordKey.currentState?.syncError(
errorText: LocaleKeys.signIn_passwordMustContain.tr(),
);
} else {
newPasswordKey.currentState?.syncError(
errorText: error.msg,
);
}
},
);
}

View File

@ -208,16 +208,8 @@ class SpaceCancelOrConfirmButton extends StatelessWidget {
borderRadius: BorderRadius.circular(8),
),
),
child: FlowyButton(
useIntrinsicWidth: true,
margin:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 9.0),
radius: BorderRadius.circular(8),
text: FlowyText.regular(
confirmButtonName,
lineHeight: 1.0,
color: Theme.of(context).colorScheme.onPrimary,
),
child: AFFilledTextButton.destructive(
text: confirmButtonName,
onTap: onConfirm,
),
),

View File

@ -4,6 +4,7 @@ import 'package:appflowy/user/application/password/password_bloc.dart';
import 'package:appflowy/workspace/presentation/settings/pages/account/password/error_extensions.dart';
import 'package:appflowy/workspace/presentation/settings/pages/account/password/password_suffix_icon.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
import 'package:appflowy_ui/appflowy_ui.dart';
import 'package:easy_localization/easy_localization.dart';
@ -359,6 +360,10 @@ class _ChangePasswordDialogContentState
newPasswordTextFieldKey.currentState?.syncError(
errorText: AFPasswordErrorExtension.getErrorMessage(error),
);
} else if (error.code == ErrorCode.NewPasswordTooWeak) {
newPasswordTextFieldKey.currentState?.syncError(
errorText: LocaleKeys.signIn_passwordMustContain.tr(),
);
} else {
newPasswordTextFieldKey.currentState?.syncError(
errorText: error.msg,

View File

@ -4,6 +4,7 @@ import 'package:appflowy/user/application/password/password_bloc.dart';
import 'package:appflowy/workspace/presentation/settings/pages/account/password/error_extensions.dart';
import 'package:appflowy/workspace/presentation/settings/pages/account/password/password_suffix_icon.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
import 'package:appflowy_ui/appflowy_ui.dart';
import 'package:easy_localization/easy_localization.dart';
@ -291,6 +292,10 @@ class _SetupPasswordDialogContentState
passwordTextFieldKey.currentState?.syncError(
errorText: AFPasswordErrorExtension.getErrorMessage(error),
);
} else if (error.code == ErrorCode.NewPasswordTooWeak) {
passwordTextFieldKey.currentState?.syncError(
errorText: LocaleKeys.signIn_passwordMustContain.tr(),
);
} else {
passwordTextFieldKey.currentState?.syncError(
errorText: error.msg,

View File

@ -17,6 +17,7 @@ import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/em
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
import 'package:appflowy_ui/appflowy_ui.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme_extension.dart';
@ -134,47 +135,9 @@ class _SearchBar extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SizedBox(
height: 36,
child: FlowyTextField(
onChanged: onSearchChanged,
textStyle: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
),
decoration: InputDecoration(
hintText: LocaleKeys.settings_shortcutsPage_searchHint.tr(),
counterText: '',
contentPadding: const EdgeInsets.symmetric(
vertical: 9,
horizontal: 16,
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.outline,
),
borderRadius: Corners.s12Border,
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.primary,
),
borderRadius: Corners.s12Border,
),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.error,
),
borderRadius: Corners.s12Border,
),
focusedErrorBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.error,
),
borderRadius: Corners.s12Border,
),
),
),
return AFTextField(
onChanged: onSearchChanged,
hintText: LocaleKeys.settings_shortcutsPage_searchHint.tr(),
);
}
}

View File

@ -1,10 +1,11 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/presentation/settings/pages/account/password/password_suffix_icon.dart';
import 'package:appflowy_ui/appflowy_ui.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
/// This is used to describe a settings input field
///
@ -104,36 +105,24 @@ class _SettingsInputFieldState extends State<SettingsInputField> {
),
if (widget.label?.isNotEmpty ?? false || widget.tooltip != null)
const VSpace(8),
SizedBox(
height: 48,
child: FlowyTextField(
focusNode: focusNode,
hintText: widget.placeholder,
controller: controller,
autoFocus: false,
obscureText: obscureText,
isDense: false,
suffixIconConstraints:
BoxConstraints.tight(const Size(23 + 18, 24)),
suffixIcon: !widget.obscureText
? null
: GestureDetector(
onTap: () => setState(() => obscureText = !obscureText),
child: Padding(
padding: const EdgeInsets.only(right: 18),
child: FlowySvg(
obscureText ? FlowySvgs.show_m : FlowySvgs.hide_m,
size: const Size(12, 15),
color: Theme.of(context).colorScheme.outline,
),
),
),
onSubmitted: widget.onSave,
onChanged: (_) {
widget.onChanged?.call(controller.text);
setState(() {});
},
),
AFTextField(
controller: controller,
focusNode: focusNode,
hintText: widget.placeholder,
obscureText: obscureText,
onSubmitted: widget.onSave,
onChanged: (_) {
widget.onChanged?.call(controller.text);
setState(() {});
},
suffixIconBuilder: (context, isObscured) => widget.obscureText
? PasswordSuffixIcon(
isObscured: isObscured,
onTap: () {
setState(() => obscureText = !obscureText);
},
)
: null,
),
if (!widget.hideActions &&
((widget.value == null && controller.text.isNotEmpty) ||

View File

@ -141,11 +141,11 @@ class _CopyLinkButton extends StatelessWidget {
);
showToastNotification(
message: LocaleKeys.document_inlineLink_copyLink.tr(),
message: LocaleKeys.shareAction_copyLinkSuccess.tr(),
);
} else {
showToastNotification(
message: 'You haven\'t generated an invite link yet.',
message: LocaleKeys.settings_appearance_members_noInviteLink.tr(),
type: ToastificationType.error,
);
}

View File

@ -30,6 +30,7 @@ class AFTextField extends StatefulWidget {
this.suffixIconConstraints,
this.size = AFTextFieldSize.l,
this.groupId = EditableText,
this.focusNode,
});
/// The hint text to display when the text field is empty.
@ -65,7 +66,7 @@ class AFTextField extends StatefulWidget {
final bool obscureText;
/// The trailing widget to display.
final Widget Function(BuildContext context, bool isObscured)?
final Widget? Function(BuildContext context, bool isObscured)?
suffixIconBuilder;
/// The size of the suffix icon.
@ -74,6 +75,9 @@ class AFTextField extends StatefulWidget {
/// The group ID for the text field.
final Object groupId;
/// The focus node for the text field.
final FocusNode? focusNode;
@override
State<AFTextField> createState() => _AFTextFieldState();
}
@ -123,6 +127,7 @@ class _AFTextFieldState extends AFTextFieldState {
Widget child = TextField(
groupId: widget.groupId,
focusNode: widget.focusNode,
controller: effectiveController,
keyboardType: widget.keyboardType,
style: theme.textStyle.body.standard(

View File

@ -98,11 +98,11 @@ packages:
dependency: "direct main"
description:
path: "."
ref: "680222f"
resolved-ref: "680222f503f90d07c08c99c42764f9b08fd0f46c"
ref: "6d5f6a7755703d8ae065e773d863a4b880073d26"
resolved-ref: "6d5f6a7755703d8ae065e773d863a4b880073d26"
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
source: git
version: "5.1.0"
version: "5.2.0"
appflowy_editor_plugins:
dependency: "direct main"
description:

View File

@ -187,7 +187,7 @@ dependency_overrides:
appflowy_editor:
git:
url: https://github.com/AppFlowy-IO/appflowy-editor.git
ref: "680222f"
ref: "6d5f6a7755703d8ae065e773d863a4b880073d26"
appflowy_editor_plugins:
git:

View File

@ -48,6 +48,7 @@
"createAccount": "Create account",
"repeatPasswordEmptyError": "Repeat password can't be empty",
"unmatchedPasswordError": "Repeat password is not the same as password",
"passwordMustContain": "Password must contain at least one letter, one number, and one symbol.",
"syncPromptMessage": "Syncing the data might take a while. Please don't close this page",
"or": "or",
"signInWithGoogle": "Continue with Google",

View File

@ -386,6 +386,18 @@ pub enum ErrorCode {
#[error("Reference resource is not available")]
WeakRefDrop = 132,
#[error("New password is too weak")]
NewPasswordTooWeak = 133,
#[error("Invalid new password")]
InvalidNewPassword = 134,
#[error("New password is too long")]
NewPasswordTooLong = 135,
#[error("New password is too short")]
NewPasswordTooShort = 136,
}
impl ErrorCode {