Feat/link regexp (#1329)

* Allow for custom regular expression validation of links. (#1048)

* Fix the default RegExp for link validation to support port and match the beginning.
pull/1331/head
widealpha 2 years ago committed by GitHub
parent 1c91430882
commit b6ec9ff5d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      flutter_quill_extensions/lib/embeds/toolbar/image_button.dart
  2. 13
      flutter_quill_extensions/lib/embeds/toolbar/image_video_utils.dart
  3. 4
      flutter_quill_extensions/lib/embeds/toolbar/video_button.dart
  4. 6
      flutter_quill_extensions/lib/flutter_quill_extensions.dart
  5. 2
      lib/src/models/rules/insert.dart
  6. 5
      lib/src/widgets/toolbar.dart
  7. 23
      lib/src/widgets/toolbar/link_style_button.dart

@ -18,6 +18,7 @@ class ImageButton extends StatelessWidget {
this.iconTheme, this.iconTheme,
this.dialogTheme, this.dialogTheme,
this.tooltip, this.tooltip,
this.linkRegExp,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -40,6 +41,7 @@ class ImageButton extends StatelessWidget {
final QuillDialogTheme? dialogTheme; final QuillDialogTheme? dialogTheme;
final String? tooltip; final String? tooltip;
final RegExp? linkRegExp;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -90,7 +92,10 @@ class ImageButton extends StatelessWidget {
void _typeLink(BuildContext context) { void _typeLink(BuildContext context) {
showDialog<String>( showDialog<String>(
context: context, context: context,
builder: (_) => LinkDialog(dialogTheme: dialogTheme), builder: (_) => LinkDialog(
dialogTheme: dialogTheme,
linkRegExp: linkRegExp,
),
).then(_linkSubmitted); ).then(_linkSubmitted);
} }

@ -10,10 +10,16 @@ import 'package:image_picker/image_picker.dart';
import '../embed_types.dart'; import '../embed_types.dart';
class LinkDialog extends StatefulWidget { class LinkDialog extends StatefulWidget {
const LinkDialog({this.dialogTheme, this.link, Key? key}) : super(key: key); const LinkDialog({
this.dialogTheme,
this.link,
this.linkRegExp,
Key? key,
}) : super(key: key);
final QuillDialogTheme? dialogTheme; final QuillDialogTheme? dialogTheme;
final String? link; final String? link;
final RegExp? linkRegExp;
@override @override
LinkDialogState createState() => LinkDialogState(); LinkDialogState createState() => LinkDialogState();
@ -22,12 +28,14 @@ class LinkDialog extends StatefulWidget {
class LinkDialogState extends State<LinkDialog> { class LinkDialogState extends State<LinkDialog> {
late String _link; late String _link;
late TextEditingController _controller; late TextEditingController _controller;
late RegExp _linkRegExp;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_link = widget.link ?? ''; _link = widget.link ?? '';
_controller = TextEditingController(text: _link); _controller = TextEditingController(text: _link);
_linkRegExp = widget.linkRegExp ?? AutoFormatMultipleLinksRule.linkRegExp;
} }
@override @override
@ -48,8 +56,7 @@ class LinkDialogState extends State<LinkDialog> {
), ),
actions: [ actions: [
TextButton( TextButton(
onPressed: _link.isNotEmpty && onPressed: _link.isNotEmpty && _linkRegExp.hasMatch(_link)
AutoFormatMultipleLinksRule.linkRegExp.hasMatch(_link)
? _applyLink ? _applyLink
: null, : null,
child: Text( child: Text(

@ -18,6 +18,7 @@ class VideoButton extends StatelessWidget {
this.iconTheme, this.iconTheme,
this.dialogTheme, this.dialogTheme,
this.tooltip, this.tooltip,
this.linkRegExp,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -39,8 +40,11 @@ class VideoButton extends StatelessWidget {
final QuillIconTheme? iconTheme; final QuillIconTheme? iconTheme;
final QuillDialogTheme? dialogTheme; final QuillDialogTheme? dialogTheme;
final String? tooltip; final String? tooltip;
final RegExp? linkRegExp;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);

@ -49,6 +49,8 @@ class FlutterQuillEmbeds {
FilePickImpl? filePickImpl, FilePickImpl? filePickImpl,
WebImagePickImpl? webImagePickImpl, WebImagePickImpl? webImagePickImpl,
WebVideoPickImpl? webVideoPickImpl, WebVideoPickImpl? webVideoPickImpl,
RegExp? imageLinkRegExp,
RegExp? videoLinkRegExp,
}) => }) =>
[ [
if (showImageButton) if (showImageButton)
@ -63,6 +65,7 @@ class FlutterQuillEmbeds {
mediaPickSettingSelector: mediaPickSettingSelector, mediaPickSettingSelector: mediaPickSettingSelector,
iconTheme: iconTheme, iconTheme: iconTheme,
dialogTheme: dialogTheme, dialogTheme: dialogTheme,
linkRegExp: imageLinkRegExp,
), ),
if (showVideoButton) if (showVideoButton)
(controller, toolbarIconSize, iconTheme, dialogTheme) => VideoButton( (controller, toolbarIconSize, iconTheme, dialogTheme) => VideoButton(
@ -76,7 +79,8 @@ class FlutterQuillEmbeds {
mediaPickSettingSelector: mediaPickSettingSelector, mediaPickSettingSelector: mediaPickSettingSelector,
iconTheme: iconTheme, iconTheme: iconTheme,
dialogTheme: dialogTheme, dialogTheme: dialogTheme,
), linkRegExp: videoLinkRegExp,
),
if ((onImagePickCallback != null || onVideoPickCallback != null) && if ((onImagePickCallback != null || onVideoPickCallback != null) &&
showCameraButton) showCameraButton)
(controller, toolbarIconSize, iconTheme, dialogTheme) => CameraButton( (controller, toolbarIconSize, iconTheme, dialogTheme) => CameraButton(

@ -330,7 +330,7 @@ class AutoFormatMultipleLinksRule extends InsertRule {
// https://example.net/ // https://example.net/
// URL generator tool (https://www.randomlists.com/urls) is used. // URL generator tool (https://www.randomlists.com/urls) is used.
static const _linkPattern = static const _linkPattern =
r'(https?:\/\/|www\.)[\w-\.]+\.[\w-\.]+(\/([\S]+)?)?'; r'^https?:\/\/[\w\-]+(\.[\w\-]+)*(:\d+)?(\/.*)?$';
static final linkRegExp = RegExp(_linkPattern, caseSensitive: false); static final linkRegExp = RegExp(_linkPattern, caseSensitive: false);
@override @override

@ -154,6 +154,10 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
/// The space occupied by toolbar divider /// The space occupied by toolbar divider
double? sectionDividerSpace, double? sectionDividerSpace,
/// Validate the legitimacy of hyperlinks
RegExp? linkRegExp,
Key? key, Key? key,
}) { }) {
final isButtonGroupShown = [ final isButtonGroupShown = [
@ -551,6 +555,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
iconTheme: iconTheme, iconTheme: iconTheme,
dialogTheme: dialogTheme, dialogTheme: dialogTheme,
afterButtonPressed: afterButtonPressed, afterButtonPressed: afterButtonPressed,
linkRegExp: linkRegExp,
), ),
if (showSearchButton) if (showSearchButton)
SearchButton( SearchButton(

@ -18,6 +18,7 @@ class LinkStyleButton extends StatefulWidget {
this.dialogTheme, this.dialogTheme,
this.afterButtonPressed, this.afterButtonPressed,
this.tooltip, this.tooltip,
this.linkRegExp,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -28,6 +29,7 @@ class LinkStyleButton extends StatefulWidget {
final QuillDialogTheme? dialogTheme; final QuillDialogTheme? dialogTheme;
final VoidCallback? afterButtonPressed; final VoidCallback? afterButtonPressed;
final String? tooltip; final String? tooltip;
final RegExp? linkRegExp;
@override @override
_LinkStyleButtonState createState() => _LinkStyleButtonState(); _LinkStyleButtonState createState() => _LinkStyleButtonState();
@ -108,7 +110,11 @@ class _LinkStyleButtonState extends State<LinkStyleButton> {
text ??= text ??=
len == 0 ? '' : widget.controller.document.getPlainText(index, len); len == 0 ? '' : widget.controller.document.getPlainText(index, len);
return _LinkDialog( return _LinkDialog(
dialogTheme: widget.dialogTheme, link: link, text: text); dialogTheme: widget.dialogTheme,
link: link,
text: text,
linkRegExp: widget.linkRegExp,
);
}, },
).then( ).then(
(value) { (value) {
@ -143,12 +149,18 @@ class _LinkStyleButtonState extends State<LinkStyleButton> {
} }
class _LinkDialog extends StatefulWidget { class _LinkDialog extends StatefulWidget {
const _LinkDialog({this.dialogTheme, this.link, this.text, Key? key}) const _LinkDialog({
: super(key: key); this.dialogTheme,
this.link,
this.text,
this.linkRegExp,
Key? key,
}) : super(key: key);
final QuillDialogTheme? dialogTheme; final QuillDialogTheme? dialogTheme;
final String? link; final String? link;
final String? text; final String? text;
final RegExp? linkRegExp;
@override @override
_LinkDialogState createState() => _LinkDialogState(); _LinkDialogState createState() => _LinkDialogState();
@ -157,6 +169,7 @@ class _LinkDialog extends StatefulWidget {
class _LinkDialogState extends State<_LinkDialog> { class _LinkDialogState extends State<_LinkDialog> {
late String _link; late String _link;
late String _text; late String _text;
late RegExp linkRegExp;
late TextEditingController _linkController; late TextEditingController _linkController;
late TextEditingController _textController; late TextEditingController _textController;
@ -165,6 +178,7 @@ class _LinkDialogState extends State<_LinkDialog> {
super.initState(); super.initState();
_link = widget.link ?? ''; _link = widget.link ?? '';
_text = widget.text ?? ''; _text = widget.text ?? '';
linkRegExp = widget.linkRegExp ?? AutoFormatMultipleLinksRule.linkRegExp;
_linkController = TextEditingController(text: _link); _linkController = TextEditingController(text: _link);
_textController = TextEditingController(text: _text); _textController = TextEditingController(text: _text);
} }
@ -218,8 +232,7 @@ class _LinkDialogState extends State<_LinkDialog> {
if (_text.isEmpty || _link.isEmpty) { if (_text.isEmpty || _link.isEmpty) {
return false; return false;
} }
if (!linkRegExp.hasMatch(_link)) {
if (!AutoFormatMultipleLinksRule.linkRegExp.hasMatch(_link)) {
return false; return false;
} }

Loading…
Cancel
Save