Allow link button to enter text

pull/602/head
X Code 3 years ago
parent c89779b4a6
commit df418e590d
  1. 8
      lib/src/models/rules/insert.dart
  2. 14
      lib/src/translations/toolbar.i18n.dart
  3. 2
      lib/src/widgets/toolbar/link_dialog.dart
  4. 124
      lib/src/widgets/toolbar/link_style_button.dart

@ -320,7 +320,7 @@ class AutoFormatMultipleLinksRule extends InsertRule {
// URL generator tool (https://www.randomlists.com/urls) is used.
static const _linkPattern =
r'(https?:\/\/|www\.)[\w-\.]+\.[\w-\.]+(\/([\S]+)?)?';
static final _linkRegExp = RegExp(_linkPattern);
static final linkRegExp = RegExp(_linkPattern);
@override
Delta? applyRule(
@ -364,7 +364,7 @@ class AutoFormatMultipleLinksRule extends InsertRule {
final affectedWords = '$leftWordPart$data$rightWordPart';
// Check for URL pattern.
final matches = _linkRegExp.allMatches(affectedWords);
final matches = linkRegExp.allMatches(affectedWords);
// If there are no matches, do not apply any format.
if (matches.isEmpty) return null;
@ -394,7 +394,7 @@ class AutoFormatMultipleLinksRule extends InsertRule {
// Keep the leading segment of text and add link with its proper
// attribute.
formatterDelta
..retain(separationLength, LinkAttribute(null).toJson())
..retain(separationLength, Attribute.link.toJson())
..retain(link.length, LinkAttribute(link).toJson());
// Update reference index.
@ -405,7 +405,7 @@ class AutoFormatMultipleLinksRule extends InsertRule {
final remainingLength = affectedWords.length - previousLinkEndRelativeIndex;
// Remove links from remaining non-link text.
formatterDelta.retain(remainingLength, LinkAttribute(null).toJson());
formatterDelta.retain(remainingLength, Attribute.link.toJson());
// Build and return resulting change delta.
return baseDelta.compose(formatterDelta);

@ -18,6 +18,7 @@ extension Localization on String {
'Zoom': 'Zoom',
'Saved': 'Saved',
'Text': 'Text',
'What is entered is not a link': 'What is entered is not a link',
},
'ar': {
'Paste a link': 'نسخ الرابط',
@ -34,6 +35,7 @@ extension Localization on String {
'Zoom': 'تكبير',
'Saved': 'أنقذ',
'Text': 'Text',
'What is entered is not a link': 'What is entered is not a link',
},
'da': {
'Paste a link': 'Indsæt link',
@ -50,6 +52,7 @@ extension Localization on String {
'Zoom': 'Zoom ind',
'Saved': 'Gemt',
'Text': 'Text',
'What is entered is not a link': 'What is entered is not a link',
},
'de': {
'Paste a link': 'Link hinzufügen',
@ -67,6 +70,7 @@ extension Localization on String {
'Zoom': 'Zoomen',
'Saved': 'Gerettet',
'Text': 'Text',
'What is entered is not a link': 'What is entered is not a link',
},
'fr': {
'Paste a link': 'Coller un lien',
@ -83,6 +87,7 @@ extension Localization on String {
'Zoom': 'Zoom',
'Saved': 'Enregistrée',
'Text': 'Text',
'What is entered is not a link': 'What is entered is not a link',
},
'zh_CN': {
'Paste a link': '粘贴链接',
@ -99,6 +104,7 @@ extension Localization on String {
'Zoom': '放大',
'Saved': '已保存',
'Text': '文字',
'What is entered is not a link': 'What is entered is not a link',
},
'ko': {
'Paste a link': '링크를 붙여넣어 주세요.',
@ -115,6 +121,7 @@ extension Localization on String {
'Zoom': '확대하기',
'Saved': '저장되었습니다.',
'Text': 'Text',
'What is entered is not a link': 'What is entered is not a link',
},
'ru': {
'Paste a link': 'Вставить ссылку',
@ -131,6 +138,7 @@ extension Localization on String {
'Zoom': 'Увеличить',
'Saved': 'Сохранено',
'Text': 'Text',
'What is entered is not a link': 'What is entered is not a link',
},
'es': {
'Paste a link': 'Pega un enlace',
@ -148,6 +156,7 @@ extension Localization on String {
'Zoom': 'Zoom',
'Saved': 'Salvado',
'Text': 'Text',
'What is entered is not a link': 'What is entered is not a link',
},
'tr': {
'Paste a link': 'Bağlantıyı Yapıştır',
@ -164,6 +173,7 @@ extension Localization on String {
'Zoom': 'yakınlaştır',
'Saved': 'kaydedildi',
'Text': 'Text',
'What is entered is not a link': 'What is entered is not a link',
},
'uk': {
'Paste a link': 'Вставити посилання',
@ -180,6 +190,7 @@ extension Localization on String {
'Zoom': 'Збільшити',
'Saved': 'Збережено',
'Text': 'Text',
'What is entered is not a link': 'What is entered is not a link',
},
'pt': {
'Paste a link': 'Colar um link',
@ -197,6 +208,7 @@ extension Localization on String {
'Zoom': 'Ampliação',
'Saved': 'Salvou',
'Text': 'Text',
'What is entered is not a link': 'What is entered is not a link',
},
'pl': {
'Paste a link': 'Wklej link',
@ -213,6 +225,7 @@ extension Localization on String {
'Zoom': 'Powiększenie',
'Saved': 'Zapisane',
'Text': 'Text',
'What is entered is not a link': 'What is entered is not a link',
},
'vi': {
'Paste a link': 'Chèn liên kết',
@ -229,6 +242,7 @@ extension Localization on String {
'Zoom': 'Thu phóng',
'Saved': 'Đã lưu',
'Text': 'Text',
'What is entered is not a link': 'What is entered is not a link',
},
};

@ -31,7 +31,7 @@ class LinkDialogState extends State<LinkDialog> {
content: TextField(
style: widget.dialogTheme?.inputTextStyle,
decoration: InputDecoration(
labelText: 'Link'.i18n,
labelText: 'Paste a link'.i18n,
labelStyle: widget.dialogTheme?.labelTextStyle,
floatingLabelStyle: widget.dialogTheme?.labelTextStyle),
autofocus: true,

@ -1,12 +1,13 @@
import 'package:flutter/material.dart';
import 'package:tuple/tuple.dart';
import '../../models/documents/attribute.dart';
import '../../models/rules/insert.dart';
import '../../models/themes/quill_dialog_theme.dart';
import '../../models/themes/quill_icon_theme.dart';
import '../../translations/toolbar.i18n.dart';
import '../controller.dart';
import '../toolbar.dart';
import 'link_dialog.dart';
class LinkStyleButton extends StatefulWidget {
const LinkStyleButton({
@ -96,11 +97,19 @@ class _LinkStyleButtonState extends State<LinkStyleButton> {
}
void _openLinkDialog(BuildContext context) {
showDialog<String>(
showDialog<dynamic>(
context: context,
builder: (ctx) {
final link = _getLinkAttributeValue();
return LinkDialog(dialogTheme: widget.dialogTheme, link: link);
if (link != null) {
// TODO: text should be the link's corresponding text, not selection
}
final index = widget.controller.selection.baseOffset;
final text = widget.controller.document
.toPlainText()
.substring(index, widget.controller.selection.extentOffset);
return _LinkDialog(
dialogTheme: widget.dialogTheme, link: link, text: text);
},
).then(_linkSubmitted);
}
@ -112,10 +121,111 @@ class _LinkStyleButtonState extends State<LinkStyleButton> {
?.value;
}
void _linkSubmitted(String? value) {
if (value == null || value.isEmpty) {
return;
void _linkSubmitted(dynamic value) {
// text.isNotEmpty && link.isNotEmpty
final String text = (value as Tuple2).item1;
final String link = value.item2;
final index = widget.controller.selection.baseOffset;
final length = widget.controller.selection.extentOffset - index;
widget.controller.replaceText(index, length, text, null);
widget.controller.formatSelection(LinkAttribute(link));
}
}
class _LinkDialog extends StatefulWidget {
const _LinkDialog({this.dialogTheme, this.link, this.text, Key? key})
: super(key: key);
final QuillDialogTheme? dialogTheme;
final String? link;
final String? text;
@override
_LinkDialogState createState() => _LinkDialogState();
}
class _LinkDialogState extends State<_LinkDialog> {
late String _link;
late String _text;
late TextEditingController _linkController;
late TextEditingController _textController;
@override
void initState() {
super.initState();
_link = widget.link ?? '';
_text = widget.text ?? '';
_linkController = TextEditingController(text: _link);
_textController = TextEditingController(text: _text);
}
@override
Widget build(BuildContext context) {
return AlertDialog(
backgroundColor: widget.dialogTheme?.dialogBackgroundColor,
content: Column(
children: [
TextField(
style: widget.dialogTheme?.inputTextStyle,
decoration: InputDecoration(
labelText: 'Text'.i18n,
labelStyle: widget.dialogTheme?.labelTextStyle,
floatingLabelStyle: widget.dialogTheme?.labelTextStyle),
autofocus: true,
onChanged: _textChanged,
controller: _textController,
),
TextField(
style: widget.dialogTheme?.inputTextStyle,
decoration: InputDecoration(
labelText: 'Link'.i18n,
labelStyle: widget.dialogTheme?.labelTextStyle,
floatingLabelStyle: widget.dialogTheme?.labelTextStyle),
autofocus: true,
onChanged: _linkChanged,
controller: _linkController,
),
],
),
actions: [
TextButton(
onPressed: _canPress() ? _applyLink : null,
child: Text(
'Ok'.i18n,
style: widget.dialogTheme?.labelTextStyle,
),
),
],
);
}
bool _canPress() {
if (_text.isEmpty || _link.isEmpty) {
return false;
}
widget.controller.formatSelection(LinkAttribute(value));
if (!AutoFormatMultipleLinksRule.linkRegExp.hasMatch(_link)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('What is entered is not a link'.i18n)));
return false;
}
return true;
}
void _linkChanged(String value) {
setState(() {
_link = value;
});
}
void _textChanged(String value) {
setState(() {
_text = value;
});
}
void _applyLink() {
Navigator.pop(context, Tuple2(_text, _link));
}
}

Loading…
Cancel
Save