diff --git a/frontend/app_flowy/packages/appflowy_editor/example/assets/example.json b/frontend/app_flowy/packages/appflowy_editor/example/assets/example.json index 2d441d3367..991c03296a 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/assets/example.json +++ b/frontend/app_flowy/packages/appflowy_editor/example/assets/example.json @@ -9,6 +9,12 @@ "align": "center" } }, + { + "type": "tex", + "attributes": { + "tex": "x = 2" + } + }, { "type": "text", "attributes": { "subtype": "heading", "heading": "h1" }, diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart index 744359052f..fecec3d3e0 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart @@ -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 { editable: true, customBuilders: { 'text/code_block': CodeBlockNodeWidgetBuilder(), + 'tex': TeXBlockNodeWidgetBuidler(), }, shortcutEvents: [ enterInCodeBlock, @@ -126,7 +128,8 @@ class _MyHomePageState extends State { underscoreToItalic, ], selectionMenuItems: [ - codeBlockItem, + codeBlockMenuItem, + teXBlockMenuItem, ], ), ); diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/tex_block_node_widget.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/tex_block_node_widget.dart new file mode 100644 index 0000000000..80ad3642ca --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/tex_block_node_widget.dart @@ -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(); + 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 { + @override + Widget build(NodeWidgetContext context) { + return _TeXBlockNodeWidget( + key: context.node.key, + node: context.node, + editorState: context.editorState, + ); + } + + @override + NodeValidator 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'), + ), + ], + ); + }, + ); + } +} diff --git a/frontend/app_flowy/packages/appflowy_editor/example/pubspec.yaml b/frontend/app_flowy/packages/appflowy_editor/example/pubspec.yaml index 9f7b4e805b..fc67a9d933 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/pubspec.yaml +++ b/frontend/app_flowy/packages/appflowy_editor/example/pubspec.yaml @@ -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: diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/l10n/intl_cz_CZ.arb b/frontend/app_flowy/packages/appflowy_editor/lib/l10n/intl_cs_CZ.arb similarity index 100% rename from frontend/app_flowy/packages/appflowy_editor/lib/l10n/intl_cz_CZ.arb rename to frontend/app_flowy/packages/appflowy_editor/lib/l10n/intl_cs_CZ.arb diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/l10n/intl_pt_PT.arb b/frontend/app_flowy/packages/appflowy_editor/lib/l10n/intl_pt_PT.arb index 4e9187f282..9b7386fd46 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/l10n/intl_pt_PT.arb +++ b/frontend/app_flowy/packages/appflowy_editor/lib/l10n/intl_pt_PT.arb @@ -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": {} } \ No newline at end of file diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_all.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_all.dart index 1dc2273626..02e3d46041 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_all.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_all.dart @@ -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 LibraryLoader(); Map _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 _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': diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_cs-CZ.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_cs-CZ.dart new file mode 100644 index 0000000000..810fe3888f --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_cs-CZ.dart @@ -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 args); + +class MessageLookup extends MessageLookupByLibrary { + String get localeName => 'cs_CZ'; + + final messages = _notInlinedMessages(_notInlinedMessages); + static Map _notInlinedMessages(_) => { + "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í") + }; +} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_fr-CA.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_fr-CA.dart index 7dd3517ac5..a7239232ed 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_fr-CA.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_fr-CA.dart @@ -22,21 +22,21 @@ class MessageLookup extends MessageLookupByLibrary { final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { - "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("l’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("souligner") }; } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_fr-FR.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_fr-FR.dart index dbe21e0e02..07e7302033 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_fr-FR.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_fr-FR.dart @@ -22,21 +22,21 @@ class MessageLookup extends MessageLookupByLibrary { final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { - "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é") }; } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_hu-HU.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_hu-HU.dart index ac9acad543..44a54b5478 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_hu-HU.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_hu-HU.dart @@ -22,21 +22,21 @@ class MessageLookup extends MessageLookupByLibrary { final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { - "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") }; } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_id-ID.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_id-ID.dart index e594bacdec..cda97336d4 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_id-ID.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_id-ID.dart @@ -22,21 +22,21 @@ class MessageLookup extends MessageLookupByLibrary { final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { - "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") }; } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_it-IT.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_it-IT.dart index 6717858291..05ee3e1353 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_it-IT.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_it-IT.dart @@ -22,21 +22,21 @@ class MessageLookup extends MessageLookupByLibrary { final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { - "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") }; } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_nl-NL.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_nl-NL.dart new file mode 100644 index 0000000000..eb096b9a7a --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_nl-NL.dart @@ -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 args); + +class MessageLookup extends MessageLookupByLibrary { + String get localeName => 'nl_NL'; + + final messages = _notInlinedMessages(_notInlinedMessages); + static Map _notInlinedMessages(_) => { + "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") + }; +} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_pt-BR.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_pt-BR.dart index c7551f9d78..22c53407ac 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_pt-BR.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_pt-BR.dart @@ -22,21 +22,22 @@ class MessageLookup extends MessageLookupByLibrary { final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { - "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") }; } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_pt-PT.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_pt-PT.dart index 19a14c2509..d2d2781580 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_pt-PT.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_pt-PT.dart @@ -22,21 +22,22 @@ class MessageLookup extends MessageLookupByLibrary { final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { - "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") }; } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/l10n.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/l10n.dart index ae80f115a1..9ef7ddf4c9 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/l10n.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/l10n.dart @@ -220,6 +220,7 @@ class AppLocalizationDelegate return const [ 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'), diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_service.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_service.dart index 4fbc480dfd..4da824db80 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_service.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_service.dart @@ -169,6 +169,14 @@ final List _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'), diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/default_text_operations/format_rich_text_style.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/default_text_operations/format_rich_text_style.dart index 0377b9e37c..b1ddbb50ae 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/default_text_operations/format_rich_text_style.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/default_text_operations/format_rich_text_style.dart @@ -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; diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart index c46b2322c7..760d7e0b6c 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart @@ -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(); + 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 = []; + 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(); + 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; +}; diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart index 827b057700..21e3e90b0d 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart @@ -257,6 +257,16 @@ List 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 diff --git a/frontend/app_flowy/packages/appflowy_editor/test/infra/test_raw_key_event.dart b/frontend/app_flowy/packages/appflowy_editor/test/infra/test_raw_key_event.dart index 5ad8ddfe3e..81fb46dff9 100644 --- a/frontend/app_flowy/packages/appflowy_editor/test/infra/test_raw_key_event.dart +++ b/frontend/app_flowy/packages/appflowy_editor/test/infra/test_raw_key_event.dart @@ -139,6 +139,9 @@ extension on LogicalKeyboardKey { if (this == LogicalKeyboardKey.keyZ) { return PhysicalKeyboardKey.keyZ; } + if (this == LogicalKeyboardKey.tilde) { + return PhysicalKeyboardKey.backquote; + } throw UnimplementedError(); } } diff --git a/frontend/app_flowy/packages/appflowy_editor/test/render/selection_menu/selection_menu_widget_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/render/selection_menu/selection_menu_widget_test.dart index 2076daefc1..9f9046ae52 100644 --- a/frontend/app_flowy/packages/appflowy_editor/test/render/selection_menu/selection_menu_widget_test.dart +++ b/frontend/app_flowy/packages/appflowy_editor/test/render/selection_menu/selection_menu_widget_test.dart @@ -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( diff --git a/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/markdown_syntax_to_styled_text_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/markdown_syntax_to_styled_text_test.dart index d11a955643..d0ca407ea1 100644 --- a/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/markdown_syntax_to_styled_text_test.dart +++ b/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/markdown_syntax_to_styled_text_test.dart @@ -150,5 +150,111 @@ void main() async { expect(textNode.toRawString(), text); }); }); + + group('convert double tilde to strikethrough', () { + Future 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); + }); + }); }); }