Selaa lähdekoodia

Merge branch 'main' into main

Enzo Lizama Paredes 2 vuotta sitten
vanhempi
commit
d2121ed2b9
24 muutettua tiedostoa jossa 680 lisäystä ja 130 poistoa
  1. 6 0
      frontend/app_flowy/packages/appflowy_editor/example/assets/example.json
  2. 4 1
      frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart
  3. 189 0
      frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/tex_block_node_widget.dart
  4. 1 0
      frontend/app_flowy/packages/appflowy_editor/example/pubspec.yaml
  5. 0 0
      frontend/app_flowy/packages/appflowy_editor/lib/l10n/intl_cs_CZ.arb
  6. 16 16
      frontend/app_flowy/packages/appflowy_editor/lib/l10n/intl_pt_PT.arb
  7. 8 0
      frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_all.dart
  8. 44 0
      frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_cs-CZ.dart
  9. 16 16
      frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_fr-CA.dart
  10. 16 16
      frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_fr-FR.dart
  11. 16 16
      frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_hu-HU.dart
  12. 16 16
      frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_id-ID.dart
  13. 16 16
      frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_it-IT.dart
  14. 43 0
      frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_nl-NL.dart
  15. 17 16
      frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_pt-BR.dart
  16. 17 16
      frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_pt-PT.dart
  17. 2 0
      frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/l10n.dart
  18. 8 0
      frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_service.dart
  19. 7 0
      frontend/app_flowy/packages/appflowy_editor/lib/src/service/default_text_operations/format_rich_text_style.dart
  20. 118 0
      frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart
  21. 10 0
      frontend/app_flowy/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart
  22. 3 0
      frontend/app_flowy/packages/appflowy_editor/test/infra/test_raw_key_event.dart
  23. 1 1
      frontend/app_flowy/packages/appflowy_editor/test/render/selection_menu/selection_menu_widget_test.dart
  24. 106 0
      frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/markdown_syntax_to_styled_text_test.dart

+ 6 - 0
frontend/app_flowy/packages/appflowy_editor/example/assets/example.json

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

+ 4 - 1
frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart

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

+ 189 - 0
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<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'),
+            ),
+          ],
+        );
+      },
+    );
+  }
+}

+ 1 - 0
frontend/app_flowy/packages/appflowy_editor/example/pubspec.yaml

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

+ 0 - 0
frontend/app_flowy/packages/appflowy_editor/lib/l10n/intl_cz_CZ.arb → frontend/app_flowy/packages/appflowy_editor/lib/l10n/intl_cs_CZ.arb


+ 16 - 16
frontend/app_flowy/packages/appflowy_editor/lib/l10n/intl_pt_PT.arb

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

+ 8 - 0
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 'package:intl/src/intl_helpers.dart';
 
 
 import 'messages_ca.dart' as messages_ca;
 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_de-DE.dart' as messages_de_de;
 import 'messages_en.dart' as messages_en;
 import 'messages_en.dart' as messages_en;
 import 'messages_es-VE.dart' as messages_es_ve;
 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_id-ID.dart' as messages_id_id;
 import 'messages_it-IT.dart' as messages_it_it;
 import 'messages_it-IT.dart' as messages_it_it;
 import 'messages_ja-JP.dart' as messages_ja_jp;
 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_pl-PL.dart' as messages_pl_pl;
 import 'messages_pt-BR.dart' as messages_pt_br;
 import 'messages_pt-BR.dart' as messages_pt_br;
 import 'messages_pt-PT.dart' as messages_pt_pt;
 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();
 typedef Future<dynamic> LibraryLoader();
 Map<String, LibraryLoader> _deferredLibraries = {
 Map<String, LibraryLoader> _deferredLibraries = {
   'ca': () => new Future.value(null),
   'ca': () => new Future.value(null),
+  'cs_CZ': () => new Future.value(null),
   'de_DE': () => new Future.value(null),
   'de_DE': () => new Future.value(null),
   'en': () => new Future.value(null),
   'en': () => new Future.value(null),
   'es_VE': () => new Future.value(null),
   'es_VE': () => new Future.value(null),
@@ -45,6 +48,7 @@ Map<String, LibraryLoader> _deferredLibraries = {
   'id_ID': () => new Future.value(null),
   'id_ID': () => new Future.value(null),
   'it_IT': () => new Future.value(null),
   'it_IT': () => new Future.value(null),
   'ja_JP': () => new Future.value(null),
   'ja_JP': () => new Future.value(null),
+  'nl_NL': () => new Future.value(null),
   'pl_PL': () => new Future.value(null),
   'pl_PL': () => new Future.value(null),
   'pt_BR': () => new Future.value(null),
   'pt_BR': () => new Future.value(null),
   'pt_PT': () => new Future.value(null),
   'pt_PT': () => new Future.value(null),
@@ -58,6 +62,8 @@ MessageLookupByLibrary? _findExact(String localeName) {
   switch (localeName) {
   switch (localeName) {
     case 'ca':
     case 'ca':
       return messages_ca.messages;
       return messages_ca.messages;
+    case 'cs_CZ':
+      return messages_cs_cz.messages;
     case 'de_DE':
     case 'de_DE':
       return messages_de_de.messages;
       return messages_de_de.messages;
     case 'en':
     case 'en':
@@ -76,6 +82,8 @@ MessageLookupByLibrary? _findExact(String localeName) {
       return messages_it_it.messages;
       return messages_it_it.messages;
     case 'ja_JP':
     case 'ja_JP':
       return messages_ja_jp.messages;
       return messages_ja_jp.messages;
+    case 'nl_NL':
+      return messages_nl_nl.messages;
     case 'pl_PL':
     case 'pl_PL':
       return messages_pl_pl.messages;
       return messages_pl_pl.messages;
     case 'pt_BR':
     case 'pt_BR':

+ 44 - 0
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<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í")
+      };
+}

+ 16 - 16
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);
   final messages = _notInlinedMessages(_notInlinedMessages);
   static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
   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("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")
       };
       };
 }
 }

+ 16 - 16
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);
   final messages = _notInlinedMessages(_notInlinedMessages);
   static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
   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é")
       };
       };
 }
 }

+ 16 - 16
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);
   final messages = _notInlinedMessages(_notInlinedMessages);
   static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
   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")
       };
       };
 }
 }

+ 16 - 16
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);
   final messages = _notInlinedMessages(_notInlinedMessages);
   static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
   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")
       };
       };
 }
 }

+ 16 - 16
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);
   final messages = _notInlinedMessages(_notInlinedMessages);
   static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
   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")
       };
       };
 }
 }

+ 43 - 0
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<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")
+      };
+}

+ 17 - 16
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);
   final messages = _notInlinedMessages(_notInlinedMessages);
   static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
   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")
       };
       };
 }
 }

+ 17 - 16
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);
   final messages = _notInlinedMessages(_notInlinedMessages);
   static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
   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")
       };
       };
 }
 }

+ 2 - 0
frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/l10n.dart

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

+ 8 - 0
frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_service.dart

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

+ 7 - 0
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(
 bool insertTextNodeAfterSelection(
     EditorState editorState, Attributes attributes) {
     EditorState editorState, Attributes attributes) {
   final selection = editorState.service.selectionService.currentSelection.value;
   final selection = editorState.service.selectionService.currentSelection.value;

+ 118 - 0
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;
   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;
+};

+ 10 - 0
frontend/app_flowy/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart

@@ -257,6 +257,16 @@ List<ShortcutEvent> builtInShortcutEvents = [
     command: 'backquote',
     command: 'backquote',
     handler: backquoteToCodeHandler,
     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
   // https://github.com/flutter/flutter/issues/104944
   // Workaround: Using space editing on the web platform often results in errors,
   // 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
   //  so adding a shortcut event to handle the space input instead of using the

+ 3 - 0
frontend/app_flowy/packages/appflowy_editor/test/infra/test_raw_key_event.dart

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

+ 1 - 1
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);
       await editor.pressLogicKey(LogicalKeyboardKey.backspace);
       expect(
       expect(
         find.byType(SelectionMenuItemWidget, skipOffstage: false),
         find.byType(SelectionMenuItemWidget, skipOffstage: false),
-        findsNWidgets(4),
+        findsNWidgets(5),
       );
       );
       await editor.pressLogicKey(LogicalKeyboardKey.keyE);
       await editor.pressLogicKey(LogicalKeyboardKey.keyE);
       expect(
       expect(

+ 106 - 0
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);
         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);
+      });
+    });
   });
   });
 }
 }