mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: image url check for embed link (#4826)
* fix: image url check in for embed link * chore: move all patterns to shared * test: prefer enterText over manipulating widget
This commit is contained in:
parent
6e2caf3358
commit
f5cb8b6d25
@ -1,6 +1,9 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_picker.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_skin_tone.dart';
|
||||
@ -14,8 +17,6 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/image/embe
|
||||
import 'package:appflowy/plugins/inline_actions/widgets/inline_actions_handler.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
@ -132,8 +133,7 @@ class EditorOperations {
|
||||
of: find.byType(EmbedImageUrlWidget),
|
||||
matching: find.byType(TextField),
|
||||
);
|
||||
final textField = tester.widget<TextField>(imageUrlTextField);
|
||||
textField.controller?.text = imageUrl;
|
||||
await tester.enterText(imageUrlTextField, imageUrl);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tapButton(
|
||||
find.descendant(
|
||||
|
@ -1,10 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/app_bar.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_search_text_field.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/util/google_font_family_extension.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
@ -23,9 +25,7 @@ class FontPickerScreen extends StatelessWidget {
|
||||
}
|
||||
|
||||
class LanguagePickerPage extends StatefulWidget {
|
||||
const LanguagePickerPage({
|
||||
super.key,
|
||||
});
|
||||
const LanguagePickerPage({super.key});
|
||||
|
||||
@override
|
||||
State<LanguagePickerPage> createState() => _LanguagePickerPageState();
|
||||
@ -52,6 +52,7 @@ class _LanguagePickerPageState extends State<LanguagePickerPage> {
|
||||
body: SafeArea(
|
||||
child: Scrollbar(
|
||||
child: ListView.builder(
|
||||
itemCount: availableFonts.length + 1, // with search bar
|
||||
itemBuilder: (context, index) {
|
||||
if (index == 0) {
|
||||
// search bar
|
||||
@ -65,7 +66,8 @@ class _LanguagePickerPageState extends State<LanguagePickerPage> {
|
||||
setState(() {
|
||||
availableFonts = _availableFonts
|
||||
.where(
|
||||
(element) => parseFontFamilyName(element)
|
||||
(font) => font
|
||||
.parseFontFamilyName()
|
||||
.toLowerCase()
|
||||
.contains(keyword.toLowerCase()),
|
||||
)
|
||||
@ -75,8 +77,9 @@ class _LanguagePickerPageState extends State<LanguagePickerPage> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final fontFamilyName = availableFonts[index - 1];
|
||||
final displayName = parseFontFamilyName(fontFamilyName);
|
||||
final displayName = fontFamilyName.parseFontFamilyName();
|
||||
return FlowyOptionTile.checkbox(
|
||||
text: displayName,
|
||||
isSelected: selectedFontFamilyName == fontFamilyName,
|
||||
@ -86,17 +89,9 @@ class _LanguagePickerPageState extends State<LanguagePickerPage> {
|
||||
backgroundColor: Colors.transparent,
|
||||
);
|
||||
},
|
||||
itemCount: availableFonts.length + 1, // with search bar
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String parseFontFamilyName(String fontFamilyName) {
|
||||
final camelCase = RegExp('(?<=[a-z])[A-Z]');
|
||||
return fontFamilyName
|
||||
.replaceAll('_regular', '')
|
||||
.replaceAllMapped(camelCase, (m) => ' ${m.group(0)}');
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import 'package:appflowy/plugins/database/grid/application/calculations/field_ty
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/calculation_selector.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/calculation_type_item.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/remove_calculation_button.dart';
|
||||
import 'package:appflowy/shared/patterns/common_patterns.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/calculation_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/number_entities.pb.dart';
|
||||
@ -132,9 +133,8 @@ class _CalculateCellState extends State<CalculateCell> {
|
||||
}
|
||||
|
||||
String _withoutTrailingZeros(String value) {
|
||||
final regex = RegExp(r'^(\d+(?:\.\d*?[1-9](?=0|\b))?)\.?0*$');
|
||||
if (regex.hasMatch(value)) {
|
||||
final match = regex.firstMatch(value)!;
|
||||
if (trailingZerosRegex.hasMatch(value)) {
|
||||
final match = trailingZerosRegex.firstMatch(value)!;
|
||||
return match.group(1)!;
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,7 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart';
|
||||
import 'package:appflowy/shared/patterns/common_patterns.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
RegExp _hrefRegex = RegExp(
|
||||
r'https?://(?:www\.)?[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}(?:/[^\s]*)?',
|
||||
);
|
||||
|
||||
extension PasteFromPlainText on EditorState {
|
||||
Future<void> pastePlainText(String plainText) async {
|
||||
if (await pasteHtmlIfAvailable(plainText)) {
|
||||
@ -23,7 +20,7 @@ extension PasteFromPlainText on EditorState {
|
||||
.map((e) {
|
||||
// parse the url content
|
||||
final Attributes attributes = {};
|
||||
if (_hrefRegex.hasMatch(e)) {
|
||||
if (hrefRegex.hasMatch(e)) {
|
||||
attributes[AppFlowyRichTextKeys.href] = e;
|
||||
}
|
||||
return Delta()..insert(e, attributes: attributes);
|
||||
@ -45,7 +42,7 @@ extension PasteFromPlainText on EditorState {
|
||||
if (selection == null ||
|
||||
!selection.isSingle ||
|
||||
selection.isCollapsed ||
|
||||
!_hrefRegex.hasMatch(plainText)) {
|
||||
!hrefRegex.hasMatch(plainText)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/shared/patterns/common_patterns.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class EmbedImageUrlWidget extends StatefulWidget {
|
||||
const EmbedImageUrlWidget({
|
||||
@ -16,6 +18,7 @@ class EmbedImageUrlWidget extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _EmbedImageUrlWidgetState extends State<EmbedImageUrlWidget> {
|
||||
bool isUrlValid = true;
|
||||
String inputText = '';
|
||||
|
||||
@override
|
||||
@ -25,8 +28,15 @@ class _EmbedImageUrlWidgetState extends State<EmbedImageUrlWidget> {
|
||||
FlowyTextField(
|
||||
hintText: LocaleKeys.document_imageBlock_embedLink_placeholder.tr(),
|
||||
onChanged: (value) => inputText = value,
|
||||
onEditingComplete: () => widget.onSubmit(inputText),
|
||||
onEditingComplete: submit,
|
||||
),
|
||||
if (!isUrlValid) ...[
|
||||
const VSpace(8),
|
||||
FlowyText(
|
||||
LocaleKeys.document_plugins_cover_invalidImageUrl.tr(),
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
],
|
||||
const VSpace(8),
|
||||
SizedBox(
|
||||
width: 160,
|
||||
@ -37,10 +47,20 @@ class _EmbedImageUrlWidgetState extends State<EmbedImageUrlWidget> {
|
||||
LocaleKeys.document_imageBlock_embedLink_label.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
onTap: () => widget.onSubmit(inputText),
|
||||
onTap: submit,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void submit() {
|
||||
if (checkUrlValidity(inputText)) {
|
||||
return widget.onSubmit(inputText);
|
||||
}
|
||||
|
||||
setState(() => isUrlValid = false);
|
||||
}
|
||||
|
||||
bool checkUrlValidity(String url) => imgUrlRegex.hasMatch(url);
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/mobile/presentation/setting/font/font_picker_screen.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/_toolbar_theme.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/util/google_font_family_extension.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -0,0 +1,23 @@
|
||||
const _trailingZerosPattern = r'^(\d+(?:\.\d*?[1-9](?=0|\b))?)\.?0*$';
|
||||
final trailingZerosRegex = RegExp(_trailingZerosPattern);
|
||||
|
||||
const _hrefPattern =
|
||||
r'https?://(?:www\.)?[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}(?:/[^\s]*)?';
|
||||
final hrefRegex = RegExp(_hrefPattern);
|
||||
|
||||
/// This pattern allows for both HTTP and HTTPS Scheme
|
||||
/// It allows for query parameters
|
||||
/// It only allows the following image extensions: .png, .jpg, .gif, .webm
|
||||
///
|
||||
const _imgUrlPattern =
|
||||
r'(https?:\/\/)([^\s(["<,>/]*)(\/)[^\s[",><]*(.png|.jpg|.gif|.webm)(\?[^\s[",><]*)?';
|
||||
final imgUrlRegex = RegExp(_imgUrlPattern);
|
||||
|
||||
const _appflowyCloudUrlPattern = r'^(https:\/\/)(.*)(\.appflowy\.cloud\/)(.*)';
|
||||
final appflowyCloudUrlRegex = RegExp(_appflowyCloudUrlPattern);
|
||||
|
||||
const _camelCasePattern = '(?<=[a-z])[A-Z]';
|
||||
final camelCaseRegex = RegExp(_camelCasePattern);
|
||||
|
||||
const _macOSVolumesPattern = '^/Volumes/[^/]+';
|
||||
final macOSVolumesRegex = RegExp(_macOSVolumesPattern);
|
@ -0,0 +1,19 @@
|
||||
/// RegExp to match Twelve Hour formats
|
||||
/// Source: https://stackoverflow.com/a/33906224
|
||||
///
|
||||
/// Matches eg: "05:05 PM", "5:50 Pm", "10:59 am", etc.
|
||||
///
|
||||
const _twelveHourTimePattern =
|
||||
r'\b((1[0-2]|0?[1-9]):([0-5][0-9]) ([AaPp][Mm]))';
|
||||
final twelveHourTimeRegex = RegExp(_twelveHourTimePattern);
|
||||
bool isTwelveHourTime(String? time) => twelveHourTimeRegex.hasMatch(time ?? '');
|
||||
|
||||
/// RegExp to match Twenty Four Hour formats
|
||||
/// Source: https://stackoverflow.com/a/7536768
|
||||
///
|
||||
/// Matches eg: "0:01", "04:59", "16:30", etc.
|
||||
///
|
||||
const _twentyFourHourtimePattern = r'^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$';
|
||||
final tewentyFourHourTimeRegex = RegExp(_twentyFourHourtimePattern);
|
||||
bool isTwentyFourHourTime(String? time) =>
|
||||
tewentyFourHourTimeRegex.hasMatch(time ?? '');
|
@ -1,7 +1,6 @@
|
||||
import 'package:appflowy/shared/patterns/common_patterns.dart';
|
||||
|
||||
extension GoogleFontsParser on String {
|
||||
String parseFontFamilyName() {
|
||||
final camelCase = RegExp('(?<=[a-z])[A-Z]');
|
||||
return replaceAll('_regular', '')
|
||||
.replaceAllMapped(camelCase, (m) => ' ${m.group(0)}');
|
||||
}
|
||||
String parseFontFamilyName() => replaceAll('_regular', '')
|
||||
.replaceAllMapped(camelCaseRegex, (m) => ' ${m.group(0)}');
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/shared/patterns/common_patterns.dart';
|
||||
|
||||
extension StringExtension on String {
|
||||
static const _specialCharacters = r'\/:*?"<>| ';
|
||||
|
||||
@ -31,8 +33,6 @@ extension StringExtension on String {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Returns if the string is a appflowy cloud url.
|
||||
bool get isAppFlowyCloudUrl {
|
||||
return RegExp(r'^(https:\/\/)(.*)(\.appflowy\.cloud\/)(.*)').hasMatch(this);
|
||||
}
|
||||
/// Returns true if the string is a appflowy cloud url.
|
||||
bool get isAppFlowyCloudUrl => appflowyCloudUrlRegex.hasMatch(this);
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:appflowy/core/config/kv.dart';
|
||||
import 'package:appflowy/core/config/kv_keys.dart';
|
||||
import 'package:appflowy/shared/patterns/common_patterns.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../../../startup/tasks/prelude.dart';
|
||||
@ -26,7 +28,7 @@ class ApplicationDataStorage {
|
||||
|
||||
if (Platform.isMacOS) {
|
||||
// remove the prefix `/Volumes/*`
|
||||
path = path.replaceFirst(RegExp('^/Volumes/[^/]+'), '');
|
||||
path = path.replaceFirst(macOSVolumesRegex, '');
|
||||
} else if (Platform.isWindows) {
|
||||
path = path.replaceAll('/', '\\');
|
||||
}
|
||||
|
@ -1,18 +0,0 @@
|
||||
/// RegExp to match Twelve Hour formats
|
||||
/// Source: https://stackoverflow.com/a/33906224
|
||||
///
|
||||
/// Matches eg: "05:05 PM", "5:50 Pm", "10:59 am", etc.
|
||||
///
|
||||
final _twelveHourTimePattern =
|
||||
RegExp(r'\b((1[0-2]|0?[1-9]):([0-5][0-9]) ([AaPp][Mm]))');
|
||||
bool isTwelveHourTime(String? time) =>
|
||||
_twelveHourTimePattern.hasMatch(time ?? '');
|
||||
|
||||
/// RegExp to match Twenty Four Hour formats
|
||||
/// Source: https://stackoverflow.com/a/7536768
|
||||
///
|
||||
/// Matches eg: "0:01", "04:59", "16:30", etc.
|
||||
///
|
||||
final _twentyFourHourtimePattern = RegExp(r'^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$');
|
||||
bool isTwentyFourHourTime(String? time) =>
|
||||
_twentyFourHourtimePattern.hasMatch(time ?? '');
|
@ -95,7 +95,7 @@ class _FontFamilyDropDownState extends State<FontFamilyDropDown> {
|
||||
return FlowySettingValueDropDown(
|
||||
popoverKey: ThemeFontFamilySetting.popoverKey,
|
||||
popoverController: widget.popoverController,
|
||||
currentValue: parseFontFamilyName(widget.currentFontFamily),
|
||||
currentValue: widget.currentFontFamily.parseFontFamilyName(),
|
||||
onClose: () {
|
||||
query.value = '';
|
||||
widget.onClose?.call();
|
||||
@ -162,18 +162,11 @@ class _FontFamilyDropDownState extends State<FontFamilyDropDown> {
|
||||
);
|
||||
}
|
||||
|
||||
String parseFontFamilyName(String fontFamilyName) {
|
||||
final camelCase = RegExp('(?<=[a-z])[A-Z]');
|
||||
return fontFamilyName
|
||||
.replaceAll('_regular', '')
|
||||
.replaceAllMapped(camelCase, (m) => ' ${m.group(0)}');
|
||||
}
|
||||
|
||||
Widget _fontFamilyItemButton(
|
||||
BuildContext context,
|
||||
TextStyle style,
|
||||
) {
|
||||
final buttonFontFamily = parseFontFamilyName(style.fontFamily!);
|
||||
final buttonFontFamily = style.fontFamily!.parseFontFamilyName();
|
||||
|
||||
return Tooltip(
|
||||
message: buttonFontFamily,
|
||||
@ -184,21 +177,19 @@ class _FontFamilyDropDownState extends State<FontFamilyDropDown> {
|
||||
child: FlowyButton(
|
||||
onHover: (_) => FocusScope.of(context).unfocus(),
|
||||
text: FlowyText.medium(
|
||||
parseFontFamilyName(style.fontFamily!),
|
||||
buttonFontFamily,
|
||||
fontFamily: style.fontFamily!,
|
||||
),
|
||||
rightIcon:
|
||||
buttonFontFamily == parseFontFamilyName(widget.currentFontFamily)
|
||||
? const FlowySvg(
|
||||
FlowySvgs.check_s,
|
||||
)
|
||||
buttonFontFamily == widget.currentFontFamily.parseFontFamilyName()
|
||||
? const FlowySvg(FlowySvgs.check_s)
|
||||
: null,
|
||||
onTap: () {
|
||||
if (widget.onFontFamilyChanged != null) {
|
||||
widget.onFontFamilyChanged!(style.fontFamily!);
|
||||
} else {
|
||||
final fontFamily = style.fontFamily!.parseFontFamilyName();
|
||||
if (parseFontFamilyName(widget.currentFontFamily) !=
|
||||
if (widget.currentFontFamily.parseFontFamilyName() !=
|
||||
buttonFontFamily) {
|
||||
context
|
||||
.read<AppearanceSettingsCubit>()
|
||||
|
Loading…
Reference in New Issue
Block a user