Merge branch 'main' into main

This commit is contained in:
Enzo Lizama Paredes 2022-10-05 23:53:58 -05:00 committed by GitHub
commit d2121ed2b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 680 additions and 130 deletions

View File

@ -9,6 +9,12 @@
"align": "center"
}
},
{
"type": "tex",
"attributes": {
"tex": "x = 2"
}
},
{
"type": "text",
"attributes": { "subtype": "heading", "heading": "h1" },

View File

@ -2,6 +2,7 @@ import 'dart:convert';
import 'dart:io';
import 'package:example/plugin/code_block_node_widget.dart';
import 'package:example/plugin/tex_block_node_widget.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -119,6 +120,7 @@ class _MyHomePageState extends State<MyHomePage> {
editable: true,
customBuilders: {
'text/code_block': CodeBlockNodeWidgetBuilder(),
'tex': TeXBlockNodeWidgetBuidler(),
},
shortcutEvents: [
enterInCodeBlock,
@ -126,7 +128,8 @@ class _MyHomePageState extends State<MyHomePage> {
underscoreToItalic,
],
selectionMenuItems: [
codeBlockItem,
codeBlockMenuItem,
teXBlockMenuItem,
],
),
);

View File

@ -0,0 +1,189 @@
import 'dart:collection';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
import 'package:flutter_math_fork/flutter_math.dart';
SelectionMenuItem teXBlockMenuItem = SelectionMenuItem(
name: 'Tex',
icon: const Icon(Icons.text_fields_rounded),
keywords: ['tex, latex, katex'],
handler: (editorState, _, __) {
final selection =
editorState.service.selectionService.currentSelection.value;
final textNodes = editorState.service.selectionService.currentSelectedNodes
.whereType<TextNode>();
if (selection == null || !selection.isCollapsed || textNodes.isEmpty) {
return;
}
final Path texNodePath;
if (textNodes.first.toRawString().isEmpty) {
texNodePath = selection.end.path;
TransactionBuilder(editorState)
..insertNode(
selection.end.path,
Node(
type: 'tex',
children: LinkedList(),
attributes: {'tex': ''},
),
)
..deleteNode(textNodes.first)
..afterSelection = selection
..commit();
} else {
texNodePath = selection.end.path.next;
TransactionBuilder(editorState)
..insertNode(
selection.end.path.next,
Node(
type: 'tex',
children: LinkedList(),
attributes: {'tex': ''},
),
)
..afterSelection = selection
..commit();
}
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
final texState =
editorState.document.nodeAtPath(texNodePath)?.key?.currentState;
if (texState != null && texState is __TeXBlockNodeWidgetState) {
texState.showEditingDialog();
}
});
},
);
class TeXBlockNodeWidgetBuidler extends NodeWidgetBuilder<Node> {
@override
Widget build(NodeWidgetContext<Node> context) {
return _TeXBlockNodeWidget(
key: context.node.key,
node: context.node,
editorState: context.editorState,
);
}
@override
NodeValidator<Node> get nodeValidator => (node) {
return node.attributes['tex'] is String;
};
}
class _TeXBlockNodeWidget extends StatefulWidget {
const _TeXBlockNodeWidget({
Key? key,
required this.node,
required this.editorState,
}) : super(key: key);
final Node node;
final EditorState editorState;
@override
State<_TeXBlockNodeWidget> createState() => __TeXBlockNodeWidgetState();
}
class __TeXBlockNodeWidgetState extends State<_TeXBlockNodeWidget> {
String get _tex => widget.node.attributes['tex'] as String;
bool _isHover = false;
@override
Widget build(BuildContext context) {
return InkWell(
onHover: (value) {
setState(() {
_isHover = value;
});
},
onTap: () {
showEditingDialog();
},
child: Stack(
children: [
_buildTex(context),
if (_isHover) _buildDeleteButton(context),
],
),
);
}
Widget _buildTex(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
padding: const EdgeInsets.symmetric(vertical: 20),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(8.0)),
color: _isHover ? Colors.grey[200] : Colors.transparent,
),
child: Center(
child: Math.tex(
_tex,
textStyle: const TextStyle(fontSize: 20),
mathStyle: MathStyle.display,
),
),
);
}
Widget _buildDeleteButton(BuildContext context) {
return Positioned(
top: -5,
right: -5,
child: IconButton(
icon: Icon(
Icons.delete_outline,
color: Colors.blue[400],
size: 16,
),
onPressed: () {
TransactionBuilder(widget.editorState)
..deleteNode(widget.node)
..commit();
},
),
);
}
void showEditingDialog() {
showDialog(
context: context,
builder: (context) {
final controller = TextEditingController(text: _tex);
return AlertDialog(
title: const Text('Edit Katex'),
content: TextField(
controller: controller,
maxLines: null,
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
Navigator.of(context).pop();
if (controller.text != _tex) {
TransactionBuilder(widget.editorState)
..updateNode(
widget.node,
{'tex': controller.text},
)
..commit();
}
},
child: const Text('OK'),
),
],
);
},
);
}
}

View File

@ -44,6 +44,7 @@ dependencies:
file_picker: ^5.0.1
universal_html: ^2.0.8
highlight: ^0.7.0
flutter_math_fork: ^0.6.3+1
dev_dependencies:
flutter_test:

View File

@ -1,35 +1,35 @@
{
"@@locale": "pt-PT",
"bold": "",
"bold": "negrito",
"@bold": {},
"bulletedList": "",
"bulletedList": "lista com marcadores",
"@bulletedList": {},
"checkbox": "",
"checkbox": "caixa de seleção",
"@checkbox": {},
"embedCode": "",
"embedCode": "Código embutido",
"@embedCode": {},
"heading1": "",
"heading1": "Cabeçallho 1",
"@heading1": {},
"heading2": "",
"heading2": "Cabeçallho 2",
"@heading2": {},
"heading3": "",
"heading3": "Cabeçallho 3",
"@heading3": {},
"highlight": "",
"highlight": "realçar",
"@highlight": {},
"image": "",
"image": "imagem",
"@image": {},
"italic": "",
"italic": "itálico",
"@italic": {},
"link": "",
"link": "link",
"@link": {},
"numberedList": "",
"numberedList": "lista numerada",
"@numberedList": {},
"quote": "",
"quote": "citar",
"@quote": {},
"strikethrough": "",
"strikethrough": "tachado",
"@strikethrough": {},
"text": "",
"text": "texto",
"@text": {},
"underline": "",
"underline": "sublinhado",
"@underline": {}
}

View File

@ -16,6 +16,7 @@ import 'package:intl/message_lookup_by_library.dart';
import 'package:intl/src/intl_helpers.dart';
import 'messages_ca.dart' as messages_ca;
import 'messages_cs-CZ.dart' as messages_cs_cz;
import 'messages_de-DE.dart' as messages_de_de;
import 'messages_en.dart' as messages_en;
import 'messages_es-VE.dart' as messages_es_ve;
@ -25,6 +26,7 @@ import 'messages_hu-HU.dart' as messages_hu_hu;
import 'messages_id-ID.dart' as messages_id_id;
import 'messages_it-IT.dart' as messages_it_it;
import 'messages_ja-JP.dart' as messages_ja_jp;
import 'messages_nl-NL.dart' as messages_nl_nl;
import 'messages_pl-PL.dart' as messages_pl_pl;
import 'messages_pt-BR.dart' as messages_pt_br;
import 'messages_pt-PT.dart' as messages_pt_pt;
@ -36,6 +38,7 @@ import 'messages_zh-TW.dart' as messages_zh_tw;
typedef Future<dynamic> LibraryLoader();
Map<String, LibraryLoader> _deferredLibraries = {
'ca': () => new Future.value(null),
'cs_CZ': () => new Future.value(null),
'de_DE': () => new Future.value(null),
'en': () => new Future.value(null),
'es_VE': () => new Future.value(null),
@ -45,6 +48,7 @@ Map<String, LibraryLoader> _deferredLibraries = {
'id_ID': () => new Future.value(null),
'it_IT': () => new Future.value(null),
'ja_JP': () => new Future.value(null),
'nl_NL': () => new Future.value(null),
'pl_PL': () => new Future.value(null),
'pt_BR': () => new Future.value(null),
'pt_PT': () => new Future.value(null),
@ -58,6 +62,8 @@ MessageLookupByLibrary? _findExact(String localeName) {
switch (localeName) {
case 'ca':
return messages_ca.messages;
case 'cs_CZ':
return messages_cs_cz.messages;
case 'de_DE':
return messages_de_de.messages;
case 'en':
@ -76,6 +82,8 @@ MessageLookupByLibrary? _findExact(String localeName) {
return messages_it_it.messages;
case 'ja_JP':
return messages_ja_jp.messages;
case 'nl_NL':
return messages_nl_nl.messages;
case 'pl_PL':
return messages_pl_pl.messages;
case 'pt_BR':

View File

@ -0,0 +1,44 @@
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
// This is a library that provides messages for a cs_CZ locale. All the
// messages from the main program should be duplicated here with the same
// function name.
// Ignore issues from commonly used lints in this file.
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
import 'package:intl/intl.dart';
import 'package:intl/message_lookup_by_library.dart';
final messages = new MessageLookup();
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
class MessageLookup extends MessageLookupByLibrary {
String get localeName => 'cs_CZ';
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
"bold": MessageLookupByLibrary.simpleMessage("Tučně"),
"bulletedList":
MessageLookupByLibrary.simpleMessage("Odrážkový seznam"),
"checkbox": MessageLookupByLibrary.simpleMessage("Zaškrtávací políčko"),
"embedCode": MessageLookupByLibrary.simpleMessage("Vložit kód"),
"heading1": MessageLookupByLibrary.simpleMessage("Nadpis 1"),
"heading2": MessageLookupByLibrary.simpleMessage("Nadpis 2"),
"heading3": MessageLookupByLibrary.simpleMessage("Nadpis 3"),
"highlight": MessageLookupByLibrary.simpleMessage("Zvýraznění"),
"image": MessageLookupByLibrary.simpleMessage("Obrázek"),
"italic": MessageLookupByLibrary.simpleMessage("Kurzíva"),
"link": MessageLookupByLibrary.simpleMessage("Odkaz"),
"numberedList":
MessageLookupByLibrary.simpleMessage("Číslovaný seznam"),
"quote": MessageLookupByLibrary.simpleMessage("Citace"),
"strikethrough": MessageLookupByLibrary.simpleMessage("Přeškrtnutí"),
"text": MessageLookupByLibrary.simpleMessage("Text"),
"underline": MessageLookupByLibrary.simpleMessage("Podtržení")
};
}

View File

@ -22,21 +22,21 @@ class MessageLookup extends MessageLookupByLibrary {
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
"bold": MessageLookupByLibrary.simpleMessage(""),
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
"checkbox": MessageLookupByLibrary.simpleMessage(""),
"embedCode": MessageLookupByLibrary.simpleMessage(""),
"heading1": MessageLookupByLibrary.simpleMessage(""),
"heading2": MessageLookupByLibrary.simpleMessage(""),
"heading3": MessageLookupByLibrary.simpleMessage(""),
"highlight": MessageLookupByLibrary.simpleMessage(""),
"image": MessageLookupByLibrary.simpleMessage(""),
"italic": MessageLookupByLibrary.simpleMessage(""),
"link": MessageLookupByLibrary.simpleMessage(""),
"numberedList": MessageLookupByLibrary.simpleMessage(""),
"quote": MessageLookupByLibrary.simpleMessage(""),
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
"text": MessageLookupByLibrary.simpleMessage(""),
"underline": MessageLookupByLibrary.simpleMessage("")
"bold": MessageLookupByLibrary.simpleMessage("gras"),
"bulletedList": MessageLookupByLibrary.simpleMessage("liste à puces"),
"checkbox": MessageLookupByLibrary.simpleMessage("case à cocher"),
"embedCode": MessageLookupByLibrary.simpleMessage("incorporer Code"),
"heading1": MessageLookupByLibrary.simpleMessage("en-tête1"),
"heading2": MessageLookupByLibrary.simpleMessage("en-tête2"),
"heading3": MessageLookupByLibrary.simpleMessage("en-tête3"),
"highlight": MessageLookupByLibrary.simpleMessage("mettre en évidence"),
"image": MessageLookupByLibrary.simpleMessage("limage"),
"italic": MessageLookupByLibrary.simpleMessage("italique"),
"link": MessageLookupByLibrary.simpleMessage("lien"),
"numberedList": MessageLookupByLibrary.simpleMessage("liste numérotée"),
"quote": MessageLookupByLibrary.simpleMessage("citation"),
"strikethrough": MessageLookupByLibrary.simpleMessage("barré"),
"text": MessageLookupByLibrary.simpleMessage("texte"),
"underline": MessageLookupByLibrary.simpleMessage("souligner")
};
}

View File

@ -22,21 +22,21 @@ class MessageLookup extends MessageLookupByLibrary {
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
"bold": MessageLookupByLibrary.simpleMessage(""),
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
"checkbox": MessageLookupByLibrary.simpleMessage(""),
"embedCode": MessageLookupByLibrary.simpleMessage(""),
"heading1": MessageLookupByLibrary.simpleMessage(""),
"heading2": MessageLookupByLibrary.simpleMessage(""),
"heading3": MessageLookupByLibrary.simpleMessage(""),
"highlight": MessageLookupByLibrary.simpleMessage(""),
"image": MessageLookupByLibrary.simpleMessage(""),
"italic": MessageLookupByLibrary.simpleMessage(""),
"link": MessageLookupByLibrary.simpleMessage(""),
"numberedList": MessageLookupByLibrary.simpleMessage(""),
"quote": MessageLookupByLibrary.simpleMessage(""),
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
"text": MessageLookupByLibrary.simpleMessage(""),
"underline": MessageLookupByLibrary.simpleMessage("")
"bold": MessageLookupByLibrary.simpleMessage("Gras"),
"bulletedList": MessageLookupByLibrary.simpleMessage("List à puces"),
"checkbox": MessageLookupByLibrary.simpleMessage("Case à cocher"),
"embedCode": MessageLookupByLibrary.simpleMessage("Incorporer code"),
"heading1": MessageLookupByLibrary.simpleMessage("Titre 1"),
"heading2": MessageLookupByLibrary.simpleMessage("Titre 2"),
"heading3": MessageLookupByLibrary.simpleMessage("Titre 3"),
"highlight": MessageLookupByLibrary.simpleMessage("Surligné"),
"image": MessageLookupByLibrary.simpleMessage("Image"),
"italic": MessageLookupByLibrary.simpleMessage("Italique"),
"link": MessageLookupByLibrary.simpleMessage("Lien"),
"numberedList": MessageLookupByLibrary.simpleMessage("Liste numérotée"),
"quote": MessageLookupByLibrary.simpleMessage("Citation"),
"strikethrough": MessageLookupByLibrary.simpleMessage("Barré"),
"text": MessageLookupByLibrary.simpleMessage("Texte"),
"underline": MessageLookupByLibrary.simpleMessage("Souligné")
};
}

View File

@ -22,21 +22,21 @@ class MessageLookup extends MessageLookupByLibrary {
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
"bold": MessageLookupByLibrary.simpleMessage(""),
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
"checkbox": MessageLookupByLibrary.simpleMessage(""),
"embedCode": MessageLookupByLibrary.simpleMessage(""),
"heading1": MessageLookupByLibrary.simpleMessage(""),
"heading2": MessageLookupByLibrary.simpleMessage(""),
"heading3": MessageLookupByLibrary.simpleMessage(""),
"highlight": MessageLookupByLibrary.simpleMessage(""),
"image": MessageLookupByLibrary.simpleMessage(""),
"italic": MessageLookupByLibrary.simpleMessage(""),
"link": MessageLookupByLibrary.simpleMessage(""),
"numberedList": MessageLookupByLibrary.simpleMessage(""),
"quote": MessageLookupByLibrary.simpleMessage(""),
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
"text": MessageLookupByLibrary.simpleMessage(""),
"underline": MessageLookupByLibrary.simpleMessage("")
"bold": MessageLookupByLibrary.simpleMessage("bátor"),
"bulletedList": MessageLookupByLibrary.simpleMessage("pontozott lista"),
"checkbox": MessageLookupByLibrary.simpleMessage("jelölőnégyzetet"),
"embedCode": MessageLookupByLibrary.simpleMessage("Beágyazás"),
"heading1": MessageLookupByLibrary.simpleMessage("címsor1"),
"heading2": MessageLookupByLibrary.simpleMessage("címsor2"),
"heading3": MessageLookupByLibrary.simpleMessage("címsor3"),
"highlight": MessageLookupByLibrary.simpleMessage("Kiemel"),
"image": MessageLookupByLibrary.simpleMessage("kép"),
"italic": MessageLookupByLibrary.simpleMessage("dőlt"),
"link": MessageLookupByLibrary.simpleMessage("link"),
"numberedList": MessageLookupByLibrary.simpleMessage("számozottLista"),
"quote": MessageLookupByLibrary.simpleMessage("idézet"),
"strikethrough": MessageLookupByLibrary.simpleMessage("áthúzott"),
"text": MessageLookupByLibrary.simpleMessage("szöveg"),
"underline": MessageLookupByLibrary.simpleMessage("aláhúzás")
};
}

View File

@ -22,21 +22,21 @@ class MessageLookup extends MessageLookupByLibrary {
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
"bold": MessageLookupByLibrary.simpleMessage(""),
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
"checkbox": MessageLookupByLibrary.simpleMessage(""),
"embedCode": MessageLookupByLibrary.simpleMessage(""),
"heading1": MessageLookupByLibrary.simpleMessage(""),
"heading2": MessageLookupByLibrary.simpleMessage(""),
"heading3": MessageLookupByLibrary.simpleMessage(""),
"highlight": MessageLookupByLibrary.simpleMessage(""),
"image": MessageLookupByLibrary.simpleMessage(""),
"italic": MessageLookupByLibrary.simpleMessage(""),
"link": MessageLookupByLibrary.simpleMessage(""),
"numberedList": MessageLookupByLibrary.simpleMessage(""),
"quote": MessageLookupByLibrary.simpleMessage(""),
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
"text": MessageLookupByLibrary.simpleMessage(""),
"underline": MessageLookupByLibrary.simpleMessage("")
"bold": MessageLookupByLibrary.simpleMessage("berani"),
"bulletedList": MessageLookupByLibrary.simpleMessage("daftar berpoin"),
"checkbox": MessageLookupByLibrary.simpleMessage("kotak centang"),
"embedCode": MessageLookupByLibrary.simpleMessage("menyematkan Kode"),
"heading1": MessageLookupByLibrary.simpleMessage("pos1"),
"heading2": MessageLookupByLibrary.simpleMessage("pos2"),
"heading3": MessageLookupByLibrary.simpleMessage("pos3"),
"highlight": MessageLookupByLibrary.simpleMessage("menyorot"),
"image": MessageLookupByLibrary.simpleMessage("gambar"),
"italic": MessageLookupByLibrary.simpleMessage("miring"),
"link": MessageLookupByLibrary.simpleMessage("tautan"),
"numberedList": MessageLookupByLibrary.simpleMessage("daftar bernomor"),
"quote": MessageLookupByLibrary.simpleMessage("mengutip"),
"strikethrough": MessageLookupByLibrary.simpleMessage("coret"),
"text": MessageLookupByLibrary.simpleMessage("teks"),
"underline": MessageLookupByLibrary.simpleMessage("menggarisbawahi")
};
}

View File

@ -22,21 +22,21 @@ class MessageLookup extends MessageLookupByLibrary {
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
"bold": MessageLookupByLibrary.simpleMessage(""),
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
"checkbox": MessageLookupByLibrary.simpleMessage(""),
"embedCode": MessageLookupByLibrary.simpleMessage(""),
"heading1": MessageLookupByLibrary.simpleMessage(""),
"heading2": MessageLookupByLibrary.simpleMessage(""),
"heading3": MessageLookupByLibrary.simpleMessage(""),
"highlight": MessageLookupByLibrary.simpleMessage(""),
"image": MessageLookupByLibrary.simpleMessage(""),
"italic": MessageLookupByLibrary.simpleMessage(""),
"link": MessageLookupByLibrary.simpleMessage(""),
"numberedList": MessageLookupByLibrary.simpleMessage(""),
"quote": MessageLookupByLibrary.simpleMessage(""),
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
"text": MessageLookupByLibrary.simpleMessage(""),
"underline": MessageLookupByLibrary.simpleMessage("")
"bold": MessageLookupByLibrary.simpleMessage("Grassetto"),
"bulletedList": MessageLookupByLibrary.simpleMessage("Elenco puntato"),
"checkbox": MessageLookupByLibrary.simpleMessage("Casella di spunta"),
"embedCode": MessageLookupByLibrary.simpleMessage("Incorpora codice"),
"heading1": MessageLookupByLibrary.simpleMessage("H1"),
"heading2": MessageLookupByLibrary.simpleMessage("H2"),
"heading3": MessageLookupByLibrary.simpleMessage("H3"),
"highlight": MessageLookupByLibrary.simpleMessage("Evidenzia"),
"image": MessageLookupByLibrary.simpleMessage("Immagine"),
"italic": MessageLookupByLibrary.simpleMessage("Corsivo"),
"link": MessageLookupByLibrary.simpleMessage("Collegamento"),
"numberedList": MessageLookupByLibrary.simpleMessage("Elenco numerato"),
"quote": MessageLookupByLibrary.simpleMessage("Cita"),
"strikethrough": MessageLookupByLibrary.simpleMessage("Barrato"),
"text": MessageLookupByLibrary.simpleMessage("Testo"),
"underline": MessageLookupByLibrary.simpleMessage("Sottolineato")
};
}

View File

@ -0,0 +1,43 @@
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
// This is a library that provides messages for a nl_NL locale. All the
// messages from the main program should be duplicated here with the same
// function name.
// Ignore issues from commonly used lints in this file.
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
import 'package:intl/intl.dart';
import 'package:intl/message_lookup_by_library.dart';
final messages = new MessageLookup();
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
class MessageLookup extends MessageLookupByLibrary {
String get localeName => 'nl_NL';
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
"bold": MessageLookupByLibrary.simpleMessage("Vet"),
"bulletedList":
MessageLookupByLibrary.simpleMessage("Opsommingstekens"),
"checkbox": MessageLookupByLibrary.simpleMessage("Selectievakje"),
"embedCode": MessageLookupByLibrary.simpleMessage("Invoegcode"),
"heading1": MessageLookupByLibrary.simpleMessage("H1"),
"heading2": MessageLookupByLibrary.simpleMessage("H2"),
"heading3": MessageLookupByLibrary.simpleMessage("H3"),
"highlight": MessageLookupByLibrary.simpleMessage("Highlight"),
"image": MessageLookupByLibrary.simpleMessage("Afbeelding"),
"italic": MessageLookupByLibrary.simpleMessage("Cursief"),
"link": MessageLookupByLibrary.simpleMessage(""),
"numberedList": MessageLookupByLibrary.simpleMessage("Nummering"),
"quote": MessageLookupByLibrary.simpleMessage("Quote"),
"strikethrough": MessageLookupByLibrary.simpleMessage("Doorhalen"),
"text": MessageLookupByLibrary.simpleMessage("Tekst"),
"underline": MessageLookupByLibrary.simpleMessage("Onderstrepen")
};
}

View File

@ -22,21 +22,22 @@ class MessageLookup extends MessageLookupByLibrary {
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
"bold": MessageLookupByLibrary.simpleMessage(""),
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
"checkbox": MessageLookupByLibrary.simpleMessage(""),
"embedCode": MessageLookupByLibrary.simpleMessage(""),
"heading1": MessageLookupByLibrary.simpleMessage(""),
"heading2": MessageLookupByLibrary.simpleMessage(""),
"heading3": MessageLookupByLibrary.simpleMessage(""),
"highlight": MessageLookupByLibrary.simpleMessage(""),
"image": MessageLookupByLibrary.simpleMessage(""),
"italic": MessageLookupByLibrary.simpleMessage(""),
"link": MessageLookupByLibrary.simpleMessage(""),
"numberedList": MessageLookupByLibrary.simpleMessage(""),
"quote": MessageLookupByLibrary.simpleMessage(""),
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
"text": MessageLookupByLibrary.simpleMessage(""),
"underline": MessageLookupByLibrary.simpleMessage("")
"bold": MessageLookupByLibrary.simpleMessage("Negrito"),
"bulletedList":
MessageLookupByLibrary.simpleMessage("Lista de marcadores"),
"checkbox": MessageLookupByLibrary.simpleMessage("Caixa de seleção"),
"embedCode": MessageLookupByLibrary.simpleMessage("Código incorporado"),
"heading1": MessageLookupByLibrary.simpleMessage("H1"),
"heading2": MessageLookupByLibrary.simpleMessage("H2"),
"heading3": MessageLookupByLibrary.simpleMessage("H3"),
"highlight": MessageLookupByLibrary.simpleMessage("Destacar"),
"image": MessageLookupByLibrary.simpleMessage("Imagem"),
"italic": MessageLookupByLibrary.simpleMessage("Itálico"),
"link": MessageLookupByLibrary.simpleMessage("Link"),
"numberedList": MessageLookupByLibrary.simpleMessage("Lista numerada"),
"quote": MessageLookupByLibrary.simpleMessage("Citar"),
"strikethrough": MessageLookupByLibrary.simpleMessage("Rasurar"),
"text": MessageLookupByLibrary.simpleMessage("Texto"),
"underline": MessageLookupByLibrary.simpleMessage("Sublinhar")
};
}

View File

@ -22,21 +22,22 @@ class MessageLookup extends MessageLookupByLibrary {
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
"bold": MessageLookupByLibrary.simpleMessage(""),
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
"checkbox": MessageLookupByLibrary.simpleMessage(""),
"embedCode": MessageLookupByLibrary.simpleMessage(""),
"heading1": MessageLookupByLibrary.simpleMessage(""),
"heading2": MessageLookupByLibrary.simpleMessage(""),
"heading3": MessageLookupByLibrary.simpleMessage(""),
"highlight": MessageLookupByLibrary.simpleMessage(""),
"image": MessageLookupByLibrary.simpleMessage(""),
"italic": MessageLookupByLibrary.simpleMessage(""),
"link": MessageLookupByLibrary.simpleMessage(""),
"numberedList": MessageLookupByLibrary.simpleMessage(""),
"quote": MessageLookupByLibrary.simpleMessage(""),
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
"text": MessageLookupByLibrary.simpleMessage(""),
"underline": MessageLookupByLibrary.simpleMessage("")
"bold": MessageLookupByLibrary.simpleMessage("negrito"),
"bulletedList":
MessageLookupByLibrary.simpleMessage("lista com marcadores"),
"checkbox": MessageLookupByLibrary.simpleMessage("caixa de seleção"),
"embedCode": MessageLookupByLibrary.simpleMessage("Código embutido"),
"heading1": MessageLookupByLibrary.simpleMessage("Cabeçallho 1"),
"heading2": MessageLookupByLibrary.simpleMessage("Cabeçallho 2"),
"heading3": MessageLookupByLibrary.simpleMessage("Cabeçallho 3"),
"highlight": MessageLookupByLibrary.simpleMessage("realçar"),
"image": MessageLookupByLibrary.simpleMessage("imagem"),
"italic": MessageLookupByLibrary.simpleMessage("itálico"),
"link": MessageLookupByLibrary.simpleMessage("link"),
"numberedList": MessageLookupByLibrary.simpleMessage("lista numerada"),
"quote": MessageLookupByLibrary.simpleMessage("citar"),
"strikethrough": MessageLookupByLibrary.simpleMessage("tachado"),
"text": MessageLookupByLibrary.simpleMessage("texto"),
"underline": MessageLookupByLibrary.simpleMessage("sublinhado")
};
}

View File

@ -220,6 +220,7 @@ class AppLocalizationDelegate
return const <Locale>[
Locale.fromSubtags(languageCode: 'en'),
Locale.fromSubtags(languageCode: 'ca'),
Locale.fromSubtags(languageCode: 'cs', countryCode: 'CZ'),
Locale.fromSubtags(languageCode: 'de', countryCode: 'DE'),
Locale.fromSubtags(languageCode: 'es', countryCode: 'VE'),
Locale.fromSubtags(languageCode: 'fr', countryCode: 'CA'),
@ -228,6 +229,7 @@ class AppLocalizationDelegate
Locale.fromSubtags(languageCode: 'id', countryCode: 'ID'),
Locale.fromSubtags(languageCode: 'it', countryCode: 'IT'),
Locale.fromSubtags(languageCode: 'ja', countryCode: 'JP'),
Locale.fromSubtags(languageCode: 'nl', countryCode: 'NL'),
Locale.fromSubtags(languageCode: 'pl', countryCode: 'PL'),
Locale.fromSubtags(languageCode: 'pt', countryCode: 'BR'),
Locale.fromSubtags(languageCode: 'pt', countryCode: 'PT'),

View File

@ -169,6 +169,14 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
insertBulletedListAfterSelection(editorState);
},
),
SelectionMenuItem(
name: () => AppFlowyEditorLocalizations.current.numberedList,
icon: _selectionMenuIcon('number'),
keywords: ['numbered list', 'list', 'ordered list'],
handler: (editorState, _, __) {
insertNumberedListAfterSelection(editorState);
},
),
SelectionMenuItem(
name: () => AppFlowyEditorLocalizations.current.checkbox,
icon: _selectionMenuIcon('checkbox'),

View File

@ -34,6 +34,13 @@ void insertBulletedListAfterSelection(EditorState editorState) {
});
}
void insertNumberedListAfterSelection(EditorState editorState) {
insertTextNodeAfterSelection(editorState, {
BuiltInAttributeKey.subtype: BuiltInAttributeKey.numberList,
BuiltInAttributeKey.number: 1,
});
}
bool insertTextNodeAfterSelection(
EditorState editorState, Attributes attributes) {
final selection = editorState.service.selectionService.currentSelection.value;

View File

@ -123,3 +123,121 @@ ShortcutEventHandler backquoteToCodeHandler = (editorState, event) {
return KeyEventResult.handled;
};
// convert ~~abc~~ to strikethrough abc.
ShortcutEventHandler doubleTildeToStrikethrough = (editorState, event) {
final selectionService = editorState.service.selectionService;
final selection = selectionService.currentSelection.value;
final textNodes = selectionService.currentSelectedNodes.whereType<TextNode>();
if (selection == null || !selection.isSingle || textNodes.length != 1) {
return KeyEventResult.ignored;
}
final textNode = textNodes.first;
final text = textNode.toRawString().substring(0, selection.end.offset);
// make sure the last two characters are ~~.
if (text.length < 2 || text[selection.end.offset - 1] != '~') {
return KeyEventResult.ignored;
}
// find all the index of `~`.
final tildeIndexes = <int>[];
for (var i = 0; i < text.length; i++) {
if (text[i] == '~') {
tildeIndexes.add(i);
}
}
if (tildeIndexes.length < 3) {
return KeyEventResult.ignored;
}
// make sure the second to last and third to last tildes are connected.
final thirdToLastTildeIndex = tildeIndexes[tildeIndexes.length - 3];
final secondToLastTildeIndex = tildeIndexes[tildeIndexes.length - 2];
final lastTildeIndex = tildeIndexes[tildeIndexes.length - 1];
if (secondToLastTildeIndex != thirdToLastTildeIndex + 1 ||
lastTildeIndex == secondToLastTildeIndex + 1) {
return KeyEventResult.ignored;
}
// delete the last three tildes.
// update the style of the text surround by `~~ ~~` to strikethrough.
// and update the cursor position.
TransactionBuilder(editorState)
..deleteText(textNode, lastTildeIndex, 1)
..deleteText(textNode, thirdToLastTildeIndex, 2)
..formatText(
textNode,
thirdToLastTildeIndex,
selection.end.offset - thirdToLastTildeIndex - 2,
{
BuiltInAttributeKey.strikethrough: true,
},
)
..afterSelection = Selection.collapsed(
Position(
path: textNode.path,
offset: selection.end.offset - 3,
),
)
..commit();
return KeyEventResult.handled;
};
/// To create a link, enclose the link text in brackets (e.g., [link text]).
/// Then, immediately follow it with the URL in parentheses (e.g., (https://example.com)).
ShortcutEventHandler markdownLinkToLinkHandler = (editorState, event) {
final selectionService = editorState.service.selectionService;
final selection = selectionService.currentSelection.value;
final textNodes = selectionService.currentSelectedNodes.whereType<TextNode>();
if (selection == null || !selection.isSingle || textNodes.length != 1) {
return KeyEventResult.ignored;
}
// find all of the indexs for important characters
final textNode = textNodes.first;
final text = textNode.toRawString();
final firstOpeningBracket = text.indexOf('[');
final firstClosingBracket = text.indexOf(']');
// use regex to validate the format of the link
// note: this enforces that the link has http or https
final regexp = RegExp(r'\[([\w\s\d]+)\]\(((?:\/|https?:\/\/)[\w\d./?=#]+)$');
final match = regexp.firstMatch(text);
if (match == null) {
return KeyEventResult.ignored;
}
// extract the text and the url of the link
final linkText = match.group(1);
final linkUrl = match.group(2);
// Delete the initial opening bracket,
// update the href attribute of the text surrounded by [ ] to the url,
// delete everything after the text,
// and update the cursor position.
TransactionBuilder(editorState)
..deleteText(textNode, firstOpeningBracket, 1)
..formatText(
textNode,
firstOpeningBracket,
firstClosingBracket - firstOpeningBracket - 1,
{
BuiltInAttributeKey.href: linkUrl,
},
)
..deleteText(textNode, firstClosingBracket - 1,
selection.end.offset - firstClosingBracket)
..afterSelection = Selection.collapsed(
Position(
path: textNode.path,
offset: firstOpeningBracket + linkText!.length,
),
)
..commit();
return KeyEventResult.handled;
};

View File

@ -257,6 +257,16 @@ List<ShortcutEvent> builtInShortcutEvents = [
command: 'backquote',
handler: backquoteToCodeHandler,
),
ShortcutEvent(
key: 'Double tilde to strikethrough',
command: 'shift+tilde',
handler: doubleTildeToStrikethrough,
),
ShortcutEvent(
key: 'Markdown link to link',
command: 'shift+parenthesis right',
handler: markdownLinkToLinkHandler,
),
// https://github.com/flutter/flutter/issues/104944
// Workaround: Using space editing on the web platform often results in errors,
// so adding a shortcut event to handle the space input instead of using the

View File

@ -139,6 +139,9 @@ extension on LogicalKeyboardKey {
if (this == LogicalKeyboardKey.keyZ) {
return PhysicalKeyboardKey.keyZ;
}
if (this == LogicalKeyboardKey.tilde) {
return PhysicalKeyboardKey.backquote;
}
throw UnimplementedError();
}
}

View File

@ -58,7 +58,7 @@ void main() async {
await editor.pressLogicKey(LogicalKeyboardKey.backspace);
expect(
find.byType(SelectionMenuItemWidget, skipOffstage: false),
findsNWidgets(4),
findsNWidgets(5),
);
await editor.pressLogicKey(LogicalKeyboardKey.keyE);
expect(

View File

@ -150,5 +150,111 @@ void main() async {
expect(textNode.toRawString(), text);
});
});
group('convert double tilde to strikethrough', () {
Future<void> insertTilde(
EditorWidgetTester editor, {
int repeat = 1,
}) async {
for (var i = 0; i < repeat; i++) {
await editor.pressLogicKey(
LogicalKeyboardKey.tilde,
isShiftPressed: true,
);
}
}
testWidgets('~~AppFlowy~~ to strikethrough AppFlowy', (tester) async {
const text = '~~AppFlowy~';
final editor = tester.editor..insertTextNode('');
await editor.startTesting();
await editor.updateSelection(
Selection.single(path: [0], startOffset: 0),
);
final textNode = editor.nodeAtPath([0]) as TextNode;
for (var i = 0; i < text.length; i++) {
await editor.insertText(textNode, text[i], i);
}
await insertTilde(editor);
final allStrikethrough = textNode.allSatisfyStrikethroughInSelection(
Selection.single(
path: [0],
startOffset: 0,
endOffset: textNode.toRawString().length,
),
);
expect(allStrikethrough, true);
expect(textNode.toRawString(), 'AppFlowy');
});
testWidgets('App~~Flowy~~ to strikethrough AppFlowy', (tester) async {
const text = 'App~~Flowy~';
final editor = tester.editor..insertTextNode('');
await editor.startTesting();
await editor.updateSelection(
Selection.single(path: [0], startOffset: 0),
);
final textNode = editor.nodeAtPath([0]) as TextNode;
for (var i = 0; i < text.length; i++) {
await editor.insertText(textNode, text[i], i);
}
await insertTilde(editor);
final allStrikethrough = textNode.allSatisfyStrikethroughInSelection(
Selection.single(
path: [0],
startOffset: 3,
endOffset: textNode.toRawString().length,
),
);
expect(allStrikethrough, true);
expect(textNode.toRawString(), 'AppFlowy');
});
testWidgets('~~~AppFlowy~~ to bold ~AppFlowy', (tester) async {
const text = '~~~AppFlowy~';
final editor = tester.editor..insertTextNode('');
await editor.startTesting();
await editor.updateSelection(
Selection.single(path: [0], startOffset: 0),
);
final textNode = editor.nodeAtPath([0]) as TextNode;
for (var i = 0; i < text.length; i++) {
await editor.insertText(textNode, text[i], i);
}
await insertTilde(editor);
final allStrikethrough = textNode.allSatisfyStrikethroughInSelection(
Selection.single(
path: [0],
startOffset: 1,
endOffset: textNode.toRawString().length,
),
);
expect(allStrikethrough, true);
expect(textNode.toRawString(), '~AppFlowy');
});
testWidgets('~~~~ nothing changes', (tester) async {
const text = '~~~';
final editor = tester.editor..insertTextNode('');
await editor.startTesting();
await editor.updateSelection(
Selection.single(path: [0], startOffset: 0),
);
final textNode = editor.nodeAtPath([0]) as TextNode;
for (var i = 0; i < text.length; i++) {
await editor.insertText(textNode, text[i], i);
}
await insertTilde(editor);
final allStrikethrough = textNode.allSatisfyStrikethroughInSelection(
Selection.single(
path: [0],
startOffset: 0,
endOffset: textNode.toRawString().length,
),
);
expect(allStrikethrough, false);
expect(textNode.toRawString(), text);
});
});
});
}