Browse Source

chore: remove flutter quill (#1426)

Nathan.fooo 2 years ago
parent
commit
42c2c4738a
27 changed files with 43 additions and 6796 deletions
  1. 0 74
      frontend/app_flowy/lib/plugins/doc/presentation/style_widgets.dart
  2. 0 97
      frontend/app_flowy/lib/plugins/doc/presentation/toolbar/check_button.dart
  3. 0 280
      frontend/app_flowy/lib/plugins/doc/presentation/toolbar/color_picker.dart
  4. 0 101
      frontend/app_flowy/lib/plugins/doc/presentation/toolbar/header_button.dart
  5. 0 33
      frontend/app_flowy/lib/plugins/doc/presentation/toolbar/history_button.dart
  6. 0 85
      frontend/app_flowy/lib/plugins/doc/presentation/toolbar/image_button.dart
  7. 0 98
      frontend/app_flowy/lib/plugins/doc/presentation/toolbar/link_button.dart
  8. 0 84
      frontend/app_flowy/lib/plugins/doc/presentation/toolbar/toggle_button.dart
  9. 0 308
      frontend/app_flowy/lib/plugins/doc/presentation/toolbar/tool_bar.dart
  10. 0 38
      frontend/app_flowy/lib/plugins/doc/presentation/toolbar/toolbar_icon_button.dart
  11. 0 133
      frontend/app_flowy/lib/plugins/doc/styles.dart
  12. 1 3
      frontend/app_flowy/lib/user/presentation/router.dart
  13. 0 30
      frontend/app_flowy/lib/workspace/application/markdown/delta_markdown.dart
  14. 0 113
      frontend/app_flowy/lib/workspace/application/markdown/src/ast.dart
  15. 0 1096
      frontend/app_flowy/lib/workspace/application/markdown/src/block_parser.dart
  16. 0 255
      frontend/app_flowy/lib/workspace/application/markdown/src/delta_markdown_decoder.dart
  17. 0 284
      frontend/app_flowy/lib/workspace/application/markdown/src/delta_markdown_encoder.dart
  18. 0 88
      frontend/app_flowy/lib/workspace/application/markdown/src/document.dart
  19. 0 1818
      frontend/app_flowy/lib/workspace/application/markdown/src/emojis.dart
  20. 0 64
      frontend/app_flowy/lib/workspace/application/markdown/src/extension_set.dart
  21. 0 121
      frontend/app_flowy/lib/workspace/application/markdown/src/html_renderer.dart
  22. 0 1270
      frontend/app_flowy/lib/workspace/application/markdown/src/inline_parser.dart
  23. 0 71
      frontend/app_flowy/lib/workspace/application/markdown/src/util.dart
  24. 0 2
      frontend/app_flowy/lib/workspace/application/markdown/src/version.dart
  25. 42 83
      frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_button.dart
  26. 0 163
      frontend/app_flowy/pubspec.lock
  27. 0 4
      frontend/app_flowy/pubspec.yaml

+ 0 - 74
frontend/app_flowy/lib/plugins/doc/presentation/style_widgets.dart

@@ -1,74 +0,0 @@
-import 'package:flowy_infra/image.dart';
-import 'package:flowy_infra/theme.dart';
-import 'package:flowy_infra_ui/style_widget/icon_button.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_quill/flutter_quill.dart';
-
-class StyleWidgetBuilder {
-  static QuillCheckboxBuilder checkbox(AppTheme theme) {
-    return EditorCheckboxBuilder(theme);
-  }
-}
-
-class EditorCheckboxBuilder extends QuillCheckboxBuilder {
-  final AppTheme theme;
-
-  EditorCheckboxBuilder(this.theme);
-
-  @override
-  Widget build(
-      {required BuildContext context,
-      required bool isChecked,
-      required ValueChanged<bool> onChanged}) {
-    return FlowyEditorCheckbox(
-      theme: theme,
-      isChecked: isChecked,
-      onChanged: onChanged,
-    );
-  }
-}
-
-class FlowyEditorCheckbox extends StatefulWidget {
-  final bool isChecked;
-  final ValueChanged<bool> onChanged;
-  final AppTheme theme;
-  const FlowyEditorCheckbox({
-    required this.theme,
-    required this.isChecked,
-    required this.onChanged,
-    Key? key,
-  }) : super(key: key);
-
-  @override
-  FlowyEditorCheckboxState createState() => FlowyEditorCheckboxState();
-}
-
-class FlowyEditorCheckboxState extends State<FlowyEditorCheckbox> {
-  late bool isChecked;
-
-  @override
-  void initState() {
-    isChecked = widget.isChecked;
-    super.initState();
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    final icon = isChecked
-        ? svgWidget('editor/editor_check')
-        : svgWidget('editor/editor_uncheck');
-    return Align(
-      alignment: Alignment.centerLeft,
-      child: FlowyIconButton(
-        onPressed: () {
-          isChecked = !isChecked;
-          widget.onChanged(isChecked);
-          setState(() {});
-        },
-        iconPadding: EdgeInsets.zero,
-        icon: icon,
-        width: 23,
-      ),
-    );
-  }
-}

+ 0 - 97
frontend/app_flowy/lib/plugins/doc/presentation/toolbar/check_button.dart

@@ -1,97 +0,0 @@
-import 'package:flutter_quill/flutter_quill.dart';
-import 'package:flutter_quill/models/documents/style.dart';
-import 'package:flutter/material.dart';
-
-import 'toolbar_icon_button.dart';
-
-class FlowyCheckListButton extends StatefulWidget {
-  const FlowyCheckListButton({
-    required this.controller,
-    required this.attribute,
-    required this.tooltipText,
-    this.iconSize = defaultIconSize,
-    this.fillColor,
-    this.childBuilder = defaultToggleStyleButtonBuilder,
-    Key? key,
-  }) : super(key: key);
-
-  final double iconSize;
-
-  final Color? fillColor;
-
-  final QuillController controller;
-
-  final ToggleStyleButtonBuilder childBuilder;
-
-  final Attribute attribute;
-
-  final String tooltipText;
-
-  @override
-  FlowyCheckListButtonState createState() => FlowyCheckListButtonState();
-}
-
-class FlowyCheckListButtonState extends State<FlowyCheckListButton> {
-  bool? _isToggled;
-
-  Style get _selectionStyle => widget.controller.getSelectionStyle();
-
-  void _didChangeEditingValue() {
-    setState(() {
-      _isToggled =
-          _getIsToggled(widget.controller.getSelectionStyle().attributes);
-    });
-  }
-
-  @override
-  void initState() {
-    super.initState();
-    _isToggled = _getIsToggled(_selectionStyle.attributes);
-    widget.controller.addListener(_didChangeEditingValue);
-  }
-
-  bool _getIsToggled(Map<String, Attribute> attrs) {
-    if (widget.attribute.key == Attribute.list.key) {
-      final attribute = attrs[widget.attribute.key];
-      if (attribute == null) {
-        return false;
-      }
-      return attribute.value == widget.attribute.value ||
-          attribute.value == Attribute.checked.value;
-    }
-    return attrs.containsKey(widget.attribute.key);
-  }
-
-  @override
-  void didUpdateWidget(covariant FlowyCheckListButton oldWidget) {
-    super.didUpdateWidget(oldWidget);
-    if (oldWidget.controller != widget.controller) {
-      oldWidget.controller.removeListener(_didChangeEditingValue);
-      widget.controller.addListener(_didChangeEditingValue);
-      _isToggled = _getIsToggled(_selectionStyle.attributes);
-    }
-  }
-
-  @override
-  void dispose() {
-    widget.controller.removeListener(_didChangeEditingValue);
-    super.dispose();
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    return ToolbarIconButton(
-      onPressed: _toggleAttribute,
-      width: widget.iconSize * kIconButtonFactor,
-      iconName: 'editor/checkbox',
-      isToggled: _isToggled ?? false,
-      tooltipText: widget.tooltipText,
-    );
-  }
-
-  void _toggleAttribute() {
-    widget.controller.formatSelection(_isToggled!
-        ? Attribute.clone(Attribute.unchecked, null)
-        : Attribute.unchecked);
-  }
-}

+ 0 - 280
frontend/app_flowy/lib/plugins/doc/presentation/toolbar/color_picker.dart

@@ -1,280 +0,0 @@
-import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_quill/flutter_quill.dart';
-import 'package:flutter_quill/models/documents/style.dart';
-import 'package:flutter_quill/utils/color.dart';
-import 'package:app_flowy/generated/locale_keys.g.dart';
-import 'package:easy_localization/easy_localization.dart';
-import 'toolbar_icon_button.dart';
-
-class FlowyColorButton extends StatefulWidget {
-  const FlowyColorButton({
-    required this.icon,
-    required this.controller,
-    required this.background,
-    this.iconSize = defaultIconSize,
-    this.iconTheme,
-    Key? key,
-  }) : super(key: key);
-
-  final IconData icon;
-  final double iconSize;
-  final bool background;
-  final QuillController controller;
-  final QuillIconTheme? iconTheme;
-
-  @override
-  FlowyColorButtonState createState() => FlowyColorButtonState();
-}
-
-class FlowyColorButtonState extends State<FlowyColorButton> {
-  late bool _isToggledColor;
-  late bool _isToggledBackground;
-  late bool _isWhite;
-  late bool _isWhitebackground;
-
-  Style get _selectionStyle => widget.controller.getSelectionStyle();
-
-  void _didChangeEditingValue() {
-    setState(() {
-      _isToggledColor =
-          _getIsToggledColor(widget.controller.getSelectionStyle().attributes);
-      _isToggledBackground = _getIsToggledBackground(
-          widget.controller.getSelectionStyle().attributes);
-      _isWhite = _isToggledColor &&
-          _selectionStyle.attributes['color']!.value == '#ffffff';
-      _isWhitebackground = _isToggledBackground &&
-          _selectionStyle.attributes['background']!.value == '#ffffff';
-    });
-  }
-
-  @override
-  void initState() {
-    super.initState();
-    _isToggledColor = _getIsToggledColor(_selectionStyle.attributes);
-    _isToggledBackground = _getIsToggledBackground(_selectionStyle.attributes);
-    _isWhite = _isToggledColor &&
-        _selectionStyle.attributes['color']!.value == '#ffffff';
-    _isWhitebackground = _isToggledBackground &&
-        _selectionStyle.attributes['background']!.value == '#ffffff';
-    widget.controller.addListener(_didChangeEditingValue);
-  }
-
-  bool _getIsToggledColor(Map<String, Attribute> attrs) {
-    return attrs.containsKey(Attribute.color.key);
-  }
-
-  bool _getIsToggledBackground(Map<String, Attribute> attrs) {
-    return attrs.containsKey(Attribute.background.key);
-  }
-
-  @override
-  void didUpdateWidget(covariant FlowyColorButton oldWidget) {
-    super.didUpdateWidget(oldWidget);
-    if (oldWidget.controller != widget.controller) {
-      oldWidget.controller.removeListener(_didChangeEditingValue);
-      widget.controller.addListener(_didChangeEditingValue);
-      _isToggledColor = _getIsToggledColor(_selectionStyle.attributes);
-      _isToggledBackground =
-          _getIsToggledBackground(_selectionStyle.attributes);
-      _isWhite = _isToggledColor &&
-          _selectionStyle.attributes['color']!.value == '#ffffff';
-      _isWhitebackground = _isToggledBackground &&
-          _selectionStyle.attributes['background']!.value == '#ffffff';
-    }
-  }
-
-  @override
-  void dispose() {
-    widget.controller.removeListener(_didChangeEditingValue);
-    super.dispose();
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    final theme = Theme.of(context);
-
-    final fillColor = _isToggledColor && !widget.background && _isWhite
-        ? stringToColor('#ffffff')
-        : (widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor);
-    final fillColorBackground =
-        _isToggledBackground && widget.background && _isWhitebackground
-            ? stringToColor('#ffffff')
-            : (widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor);
-
-    return Tooltip(
-      message: LocaleKeys.toolbar_highlight.tr(),
-      showDuration: Duration.zero,
-      child: QuillIconButton(
-        highlightElevation: 0,
-        hoverElevation: 0,
-        size: widget.iconSize * kIconButtonFactor,
-        icon: Icon(widget.icon,
-            size: widget.iconSize, color: theme.iconTheme.color),
-        fillColor: widget.background ? fillColorBackground : fillColor,
-        onPressed: _showColorPicker,
-      ),
-    );
-  }
-
-  void _changeColor(BuildContext context, Color color) {
-    var hex = color.value.toRadixString(16);
-    if (hex.startsWith('ff')) {
-      hex = hex.substring(2);
-    }
-    hex = '#$hex';
-    widget.controller.formatSelection(
-        widget.background ? BackgroundAttribute(hex) : ColorAttribute(hex));
-    Navigator.of(context).pop();
-  }
-
-  void _showColorPicker() {
-    final style = widget.controller.getSelectionStyle();
-    final values = style.values
-        .where((v) => v.key == Attribute.background.key)
-        .map((v) => v.value);
-    int initialColor = 0;
-    if (values.isNotEmpty) {
-      assert(values.length == 1);
-      initialColor = stringToHex(values.first);
-    }
-
-    StyledDialog(
-      child: SingleChildScrollView(
-        child: FlowyColorPicker(
-          onColorChanged: (color) {
-            if (color == null) {
-              widget.controller.formatSelection(BackgroundAttribute(null));
-              Navigator.of(context).pop();
-            } else {
-              _changeColor(context, color);
-            }
-          },
-          initialColor: initialColor,
-        ),
-      ),
-    ).show(context);
-  }
-}
-
-int stringToHex(String code) {
-  return int.parse(code.substring(1, 7), radix: 16) + 0xFF000000;
-}
-
-class FlowyColorPicker extends StatefulWidget {
-  final List<int> colors = [
-    0xffe8e0ff,
-    0xffffe7fd,
-    0xffffe7ee,
-    0xffffefe3,
-    0xfffff2cd,
-    0xfff5ffdc,
-    0xffddffd6,
-    0xffdefff1,
-  ];
-  final Function(Color?) onColorChanged;
-  final int initialColor;
-  FlowyColorPicker(
-      {Key? key, required this.onColorChanged, this.initialColor = 0})
-      : super(key: key);
-
-  @override
-  State<FlowyColorPicker> createState() => _FlowyColorPickerState();
-}
-
-// if (shrinkWrap) {
-//       innerContent = IntrinsicWidth(child: IntrinsicHeight(child: innerContent));
-//     }
-class _FlowyColorPickerState extends State<FlowyColorPicker> {
-  @override
-  Widget build(BuildContext context) {
-    const double width = 480;
-    const int crossAxisCount = 6;
-    const double mainAxisSpacing = 10;
-    const double crossAxisSpacing = 10;
-    final numberOfRows = (widget.colors.length / crossAxisCount).ceil();
-
-    const perRowHeight =
-        ((width - ((crossAxisCount - 1) * mainAxisSpacing)) / crossAxisCount);
-    final totalHeight =
-        numberOfRows * perRowHeight + numberOfRows * crossAxisSpacing;
-
-    return Container(
-      constraints: BoxConstraints.tightFor(width: width, height: totalHeight),
-      child: CustomScrollView(
-        scrollDirection: Axis.vertical,
-        controller: ScrollController(),
-        physics: const ClampingScrollPhysics(),
-        slivers: [
-          SliverGrid(
-            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
-              crossAxisCount: crossAxisCount,
-              mainAxisSpacing: mainAxisSpacing,
-              crossAxisSpacing: crossAxisSpacing,
-              childAspectRatio: 1.0,
-            ),
-            delegate: SliverChildBuilderDelegate(
-              (BuildContext context, int index) {
-                if (widget.colors.length > index) {
-                  final isSelected =
-                      widget.colors[index] == widget.initialColor;
-                  return ColorItem(
-                    color: Color(widget.colors[index]),
-                    onPressed: widget.onColorChanged,
-                    isSelected: isSelected,
-                  );
-                } else {
-                  return null;
-                }
-              },
-              childCount: widget.colors.length,
-            ),
-          ),
-        ],
-      ),
-    );
-  }
-}
-
-class ColorItem extends StatelessWidget {
-  final Function(Color?) onPressed;
-  final bool isSelected;
-  final Color color;
-  const ColorItem({
-    Key? key,
-    required this.color,
-    required this.onPressed,
-    this.isSelected = false,
-  }) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    if (!isSelected) {
-      return RawMaterialButton(
-        onPressed: () {
-          onPressed(color);
-        },
-        elevation: 0,
-        hoverElevation: 0.6,
-        fillColor: color,
-        shape: const CircleBorder(),
-      );
-    } else {
-      return RawMaterialButton(
-        shape: const CircleBorder(
-                side: BorderSide(color: Colors.white, width: 8)) +
-            CircleBorder(side: BorderSide(color: color, width: 4)),
-        onPressed: () {
-          if (isSelected) {
-            onPressed(null);
-          } else {
-            onPressed(color);
-          }
-        },
-        elevation: 1.0,
-        hoverElevation: 0.6,
-        fillColor: color,
-      );
-    }
-  }
-}

+ 0 - 101
frontend/app_flowy/lib/plugins/doc/presentation/toolbar/header_button.dart

@@ -1,101 +0,0 @@
-import 'package:flutter_quill/flutter_quill.dart';
-import 'package:flutter_quill/models/documents/style.dart';
-import 'package:flutter/material.dart';
-import 'package:app_flowy/generated/locale_keys.g.dart';
-import 'package:easy_localization/easy_localization.dart';
-import 'toolbar_icon_button.dart';
-
-class FlowyHeaderStyleButton extends StatefulWidget {
-  const FlowyHeaderStyleButton({
-    required this.controller,
-    this.iconSize = defaultIconSize,
-    Key? key,
-  }) : super(key: key);
-
-  final QuillController controller;
-  final double iconSize;
-
-  @override
-  FlowyHeaderStyleButtonState createState() => FlowyHeaderStyleButtonState();
-}
-
-class FlowyHeaderStyleButtonState extends State<FlowyHeaderStyleButton> {
-  Attribute? _value;
-
-  Style get _selectionStyle => widget.controller.getSelectionStyle();
-
-  @override
-  void initState() {
-    super.initState();
-    setState(() {
-      _value =
-          _selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
-    });
-    widget.controller.addListener(_didChangeEditingValue);
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    final valueToText = <Attribute, String>{
-      Attribute.h1: 'H1',
-      Attribute.h2: 'H2',
-      Attribute.h3: 'H3',
-    };
-
-    final valueAttribute = <Attribute>[
-      Attribute.h1,
-      Attribute.h2,
-      Attribute.h3
-    ];
-    final valueString = <String>['H1', 'H2', 'H3'];
-    final attributeImageName = <String>['editor/H1', 'editor/H2', 'editor/H3'];
-
-    return Row(
-      mainAxisSize: MainAxisSize.min,
-      children: List.generate(3, (index) {
-        // final child =
-        //     _valueToText[_value] == _valueString[index] ? svg('editor/H1', color: Colors.white) : svg('editor/H1');
-
-        final headerTitle = "${LocaleKeys.toolbar_header.tr()} ${index + 1}";
-        final isToggled = valueToText[_value] == valueString[index];
-        return ToolbarIconButton(
-          onPressed: () {
-            if (isToggled) {
-              widget.controller.formatSelection(Attribute.header);
-            } else {
-              widget.controller.formatSelection(valueAttribute[index]);
-            }
-          },
-          width: widget.iconSize * kIconButtonFactor,
-          iconName: attributeImageName[index],
-          isToggled: isToggled,
-          tooltipText: headerTitle,
-        );
-      }),
-    );
-  }
-
-  void _didChangeEditingValue() {
-    setState(() {
-      _value =
-          _selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
-    });
-  }
-
-  @override
-  void didUpdateWidget(covariant FlowyHeaderStyleButton oldWidget) {
-    super.didUpdateWidget(oldWidget);
-    if (oldWidget.controller != widget.controller) {
-      oldWidget.controller.removeListener(_didChangeEditingValue);
-      widget.controller.addListener(_didChangeEditingValue);
-      _value =
-          _selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
-    }
-  }
-
-  @override
-  void dispose() {
-    widget.controller.removeListener(_didChangeEditingValue);
-    super.dispose();
-  }
-}

+ 0 - 33
frontend/app_flowy/lib/plugins/doc/presentation/toolbar/history_button.dart

@@ -1,33 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_quill/flutter_quill.dart';
-
-class FlowyHistoryButton extends StatelessWidget {
-  final IconData icon;
-  final double iconSize;
-  final bool undo;
-  final QuillController controller;
-  final String tooltipText;
-
-  const FlowyHistoryButton({
-    required this.icon,
-    required this.controller,
-    required this.undo,
-    required this.tooltipText,
-    required this.iconSize,
-    Key? key,
-  }) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    return Tooltip(
-      message: tooltipText,
-      showDuration: Duration.zero,
-      child: HistoryButton(
-        icon: icon,
-        iconSize: iconSize,
-        controller: controller,
-        undo: undo,
-      ),
-    );
-  }
-}

+ 0 - 85
frontend/app_flowy/lib/plugins/doc/presentation/toolbar/image_button.dart

@@ -1,85 +0,0 @@
-import 'package:flutter_quill/flutter_quill.dart';
-import 'package:flutter/material.dart';
-import 'toolbar_icon_button.dart';
-
-class FlowyImageButton extends StatelessWidget {
-  const FlowyImageButton({
-    required this.controller,
-    required this.tooltipText,
-    this.iconSize = defaultIconSize,
-    this.onImagePickCallback,
-    this.fillColor,
-    this.filePickImpl,
-    this.webImagePickImpl,
-    this.mediaPickSettingSelector,
-    Key? key,
-  }) : super(key: key);
-
-  final double iconSize;
-
-  final Color? fillColor;
-
-  final QuillController controller;
-
-  final OnImagePickCallback? onImagePickCallback;
-
-  final WebImagePickImpl? webImagePickImpl;
-
-  final FilePickImpl? filePickImpl;
-
-  final MediaPickSettingSelector? mediaPickSettingSelector;
-
-  final String tooltipText;
-
-  @override
-  Widget build(BuildContext context) {
-    return ToolbarIconButton(
-      iconName: 'editor/image',
-      width: iconSize * 1.77,
-      onPressed: () => _onPressedHandler(context),
-      isToggled: false,
-      tooltipText: tooltipText,
-    );
-  }
-
-  Future<void> _onPressedHandler(BuildContext context) async {
-    // if (onImagePickCallback != null) {
-    //   final selector = mediaPickSettingSelector ?? ImageVideoUtils.selectMediaPickSetting;
-    //   final source = await selector(context);
-    //   if (source != null) {
-    //     if (source == MediaPickSetting.Gallery) {
-    //       _pickImage(context);
-    //     } else {
-    //       _typeLink(context);
-    //     }
-    //   }
-    // } else {
-    //   _typeLink(context);
-    // }
-  }
-
-  // void _pickImage(BuildContext context) => ImageVideoUtils.handleImageButtonTap(
-  //       context,
-  //       controller,
-  //       ImageSource.gallery,
-  //       onImagePickCallback!,
-  //       filePickImpl: filePickImpl,
-  //       webImagePickImpl: webImagePickImpl,
-  //     );
-
-  // void _typeLink(BuildContext context) {
-  //   TextFieldDialog(
-  //     title: 'URL',
-  //     value: "",
-  //     confirm: (newValue) {
-  //       if (newValue.isEmpty) {
-  //         return;
-  //       }
-  //       final index = controller.selection.baseOffset;
-  //       final length = controller.selection.extentOffset - index;
-
-  //       controller.replaceText(index, length, BlockEmbed.image(newValue), null);
-  //     },
-  //   ).show(context);
-  // }
-}

+ 0 - 98
frontend/app_flowy/lib/plugins/doc/presentation/toolbar/link_button.dart

@@ -1,98 +0,0 @@
-import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
-import 'package:flutter_quill/flutter_quill.dart';
-import 'package:flowy_infra/image.dart';
-import 'package:flowy_infra/theme.dart';
-import 'package:flowy_infra_ui/style_widget/icon_button.dart';
-import 'package:flutter/material.dart';
-import 'package:provider/provider.dart';
-
-import 'toolbar_icon_button.dart';
-
-class FlowyLinkStyleButton extends StatefulWidget {
-  const FlowyLinkStyleButton({
-    required this.controller,
-    this.iconSize = defaultIconSize,
-    Key? key,
-  }) : super(key: key);
-
-  final QuillController controller;
-  final double iconSize;
-
-  @override
-  FlowyLinkStyleButtonState createState() => FlowyLinkStyleButtonState();
-}
-
-class FlowyLinkStyleButtonState extends State<FlowyLinkStyleButton> {
-  void _didChangeSelection() {
-    setState(() {});
-  }
-
-  @override
-  void initState() {
-    super.initState();
-    widget.controller.addListener(_didChangeSelection);
-  }
-
-  @override
-  void didUpdateWidget(covariant FlowyLinkStyleButton oldWidget) {
-    super.didUpdateWidget(oldWidget);
-    if (oldWidget.controller != widget.controller) {
-      oldWidget.controller.removeListener(_didChangeSelection);
-      widget.controller.addListener(_didChangeSelection);
-    }
-  }
-
-  @override
-  void dispose() {
-    super.dispose();
-    widget.controller.removeListener(_didChangeSelection);
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    final theme = context.watch<AppTheme>();
-    final isEnabled = !widget.controller.selection.isCollapsed;
-    final pressedHandler = isEnabled ? () => _openLinkDialog(context) : null;
-    final icon = isEnabled
-        ? svgWidget(
-            'editor/share',
-            color: theme.iconColor,
-          )
-        : svgWidget(
-            'editor/share',
-            color: theme.disableIconColor,
-          );
-
-    return FlowyIconButton(
-      onPressed: pressedHandler,
-      iconPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
-      icon: icon,
-      fillColor: theme.shader6,
-      hoverColor: theme.shader5,
-      width: widget.iconSize * kIconButtonFactor,
-    );
-  }
-
-  void _openLinkDialog(BuildContext context) {
-    final style = widget.controller.getSelectionStyle();
-    final values = style.values
-        .where((v) => v.key == Attribute.link.key)
-        .map((v) => v.value);
-    String value = "";
-    if (values.isNotEmpty) {
-      assert(values.length == 1);
-      value = values.first;
-    }
-
-    NavigatorTextFieldDialog(
-      title: 'URL',
-      value: value,
-      confirm: (newValue) {
-        if (newValue.isEmpty) {
-          return;
-        }
-        widget.controller.formatSelection(LinkAttribute(newValue));
-      },
-    ).show(context);
-  }
-}

+ 0 - 84
frontend/app_flowy/lib/plugins/doc/presentation/toolbar/toggle_button.dart

@@ -1,84 +0,0 @@
-import 'package:flutter_quill/flutter_quill.dart';
-import 'package:flutter_quill/models/documents/style.dart';
-import 'package:flutter/material.dart';
-
-import 'toolbar_icon_button.dart';
-
-class FlowyToggleStyleButton extends StatefulWidget {
-  final Attribute attribute;
-  final String normalIcon;
-  final double iconSize;
-  final QuillController controller;
-  final String tooltipText;
-
-  const FlowyToggleStyleButton({
-    required this.attribute,
-    required this.normalIcon,
-    required this.controller,
-    required this.tooltipText,
-    this.iconSize = defaultIconSize,
-    Key? key,
-  }) : super(key: key);
-
-  @override
-  ToggleStyleButtonState createState() => ToggleStyleButtonState();
-}
-
-class ToggleStyleButtonState extends State<FlowyToggleStyleButton> {
-  bool? _isToggled;
-  Style get _selectionStyle => widget.controller.getSelectionStyle();
-  @override
-  void initState() {
-    super.initState();
-    _isToggled = _getIsToggled(_selectionStyle.attributes);
-    widget.controller.addListener(_didChangeEditingValue);
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    return ToolbarIconButton(
-      onPressed: _toggleAttribute,
-      width: widget.iconSize * kIconButtonFactor,
-      isToggled: _isToggled ?? false,
-      iconName: widget.normalIcon,
-      tooltipText: widget.tooltipText,
-    );
-  }
-
-  @override
-  void didUpdateWidget(covariant FlowyToggleStyleButton oldWidget) {
-    super.didUpdateWidget(oldWidget);
-    if (oldWidget.controller != widget.controller) {
-      oldWidget.controller.removeListener(_didChangeEditingValue);
-      widget.controller.addListener(_didChangeEditingValue);
-      _isToggled = _getIsToggled(_selectionStyle.attributes);
-    }
-  }
-
-  @override
-  void dispose() {
-    widget.controller.removeListener(_didChangeEditingValue);
-    super.dispose();
-  }
-
-  void _didChangeEditingValue() {
-    setState(() => _isToggled = _getIsToggled(_selectionStyle.attributes));
-  }
-
-  bool _getIsToggled(Map<String, Attribute> attrs) {
-    if (widget.attribute.key == Attribute.list.key) {
-      final attribute = attrs[widget.attribute.key];
-      if (attribute == null) {
-        return false;
-      }
-      return attribute.value == widget.attribute.value;
-    }
-    return attrs.containsKey(widget.attribute.key);
-  }
-
-  void _toggleAttribute() {
-    widget.controller.formatSelection(_isToggled!
-        ? Attribute.clone(widget.attribute, null)
-        : widget.attribute);
-  }
-}

+ 0 - 308
frontend/app_flowy/lib/plugins/doc/presentation/toolbar/tool_bar.dart

@@ -1,308 +0,0 @@
-import 'dart:async';
-import 'dart:math';
-
-import 'package:app_flowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart';
-import 'package:easy_localization/easy_localization.dart';
-import 'package:flutter_quill/flutter_quill.dart';
-import 'package:flutter/material.dart';
-import 'package:styled_widget/styled_widget.dart';
-import 'check_button.dart';
-import 'color_picker.dart';
-import 'header_button.dart';
-import 'history_button.dart';
-import 'link_button.dart';
-import 'toggle_button.dart';
-import 'toolbar_icon_button.dart';
-import 'package:app_flowy/generated/locale_keys.g.dart';
-
-class EditorToolbar extends StatelessWidget implements PreferredSizeWidget {
-  final List<Widget> children;
-  final double toolBarHeight;
-  final Color? color;
-
-  const EditorToolbar({
-    required this.children,
-    this.toolBarHeight = 46,
-    this.color,
-    Key? key,
-  }) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    return Container(
-      color: Theme.of(context).canvasColor,
-      constraints: BoxConstraints.tightFor(height: preferredSize.height),
-      child: ToolbarButtonList(buttons: children)
-          .padding(horizontal: 4, vertical: 4),
-    );
-  }
-
-  @override
-  Size get preferredSize => Size.fromHeight(toolBarHeight);
-
-  factory EditorToolbar.basic({
-    required QuillController controller,
-    double toolbarIconSize = defaultIconSize,
-    OnImagePickCallback? onImagePickCallback,
-    OnVideoPickCallback? onVideoPickCallback,
-    MediaPickSettingSelector? mediaPickSettingSelector,
-    FilePickImpl? filePickImpl,
-    WebImagePickImpl? webImagePickImpl,
-    WebVideoPickImpl? webVideoPickImpl,
-    Key? key,
-  }) {
-    return EditorToolbar(
-      key: key,
-      toolBarHeight: toolbarIconSize * 2,
-      children: [
-        FlowyHistoryButton(
-          icon: Icons.undo_outlined,
-          iconSize: toolbarIconSize,
-          controller: controller,
-          undo: true,
-          tooltipText: LocaleKeys.toolbar_undo.tr(),
-        ),
-        FlowyHistoryButton(
-          icon: Icons.redo_outlined,
-          iconSize: toolbarIconSize,
-          controller: controller,
-          undo: false,
-          tooltipText: LocaleKeys.toolbar_redo.tr(),
-        ),
-        FlowyToggleStyleButton(
-          attribute: Attribute.bold,
-          normalIcon: 'editor/bold',
-          iconSize: toolbarIconSize,
-          controller: controller,
-          tooltipText: LocaleKeys.toolbar_bold.tr(),
-        ),
-        FlowyToggleStyleButton(
-          attribute: Attribute.italic,
-          normalIcon: 'editor/italic',
-          iconSize: toolbarIconSize,
-          controller: controller,
-          tooltipText: LocaleKeys.toolbar_italic.tr(),
-        ),
-        FlowyToggleStyleButton(
-          attribute: Attribute.underline,
-          normalIcon: 'editor/underline',
-          iconSize: toolbarIconSize,
-          controller: controller,
-          tooltipText: LocaleKeys.toolbar_underline.tr(),
-        ),
-        FlowyToggleStyleButton(
-          attribute: Attribute.strikeThrough,
-          normalIcon: 'editor/strikethrough',
-          iconSize: toolbarIconSize,
-          controller: controller,
-          tooltipText: LocaleKeys.toolbar_strike.tr(),
-        ),
-        FlowyColorButton(
-          icon: Icons.format_color_fill,
-          iconSize: toolbarIconSize,
-          controller: controller,
-          background: true,
-        ),
-        // FlowyImageButton(
-        //   iconSize: toolbarIconSize,
-        //   controller: controller,
-        //   onImagePickCallback: onImagePickCallback,
-        //   filePickImpl: filePickImpl,
-        //   webImagePickImpl: webImagePickImpl,
-        //   mediaPickSettingSelector: mediaPickSettingSelector,
-        // ),
-        FlowyHeaderStyleButton(
-          controller: controller,
-          iconSize: toolbarIconSize,
-        ),
-        FlowyToggleStyleButton(
-          attribute: Attribute.ol,
-          controller: controller,
-          normalIcon: 'editor/numbers',
-          iconSize: toolbarIconSize,
-          tooltipText: LocaleKeys.toolbar_numList.tr(),
-        ),
-        FlowyToggleStyleButton(
-          attribute: Attribute.ul,
-          controller: controller,
-          normalIcon: 'editor/bullet_list',
-          iconSize: toolbarIconSize,
-          tooltipText: LocaleKeys.toolbar_bulletList.tr(),
-        ),
-        FlowyCheckListButton(
-          attribute: Attribute.unchecked,
-          controller: controller,
-          iconSize: toolbarIconSize,
-          tooltipText: LocaleKeys.toolbar_checkList.tr(),
-        ),
-        FlowyToggleStyleButton(
-          attribute: Attribute.inlineCode,
-          controller: controller,
-          normalIcon: 'editor/inline_block',
-          iconSize: toolbarIconSize,
-          tooltipText: LocaleKeys.toolbar_inlineCode.tr(),
-        ),
-        FlowyToggleStyleButton(
-          attribute: Attribute.blockQuote,
-          controller: controller,
-          normalIcon: 'editor/quote',
-          iconSize: toolbarIconSize,
-          tooltipText: LocaleKeys.toolbar_quote.tr(),
-        ),
-        FlowyLinkStyleButton(
-          controller: controller,
-          iconSize: toolbarIconSize,
-        ),
-        FlowyEmojiStyleButton(
-          normalIcon: 'editor/insert_emoticon',
-          controller: controller,
-          tooltipText: "Emoji Picker",
-        ),
-      ],
-    );
-  }
-}
-
-class ToolbarButtonList extends StatefulWidget {
-  const ToolbarButtonList({required this.buttons, Key? key}) : super(key: key);
-
-  final List<Widget> buttons;
-
-  @override
-  ToolbarButtonListState createState() => ToolbarButtonListState();
-}
-
-class ToolbarButtonListState extends State<ToolbarButtonList>
-    with WidgetsBindingObserver {
-  final ScrollController _controller = ScrollController();
-  bool _showLeftArrow = false;
-  bool _showRightArrow = false;
-
-  @override
-  void initState() {
-    super.initState();
-    _controller.addListener(_handleScroll);
-
-    // Listening to the WidgetsBinding instance is necessary so that we can
-    // hide the arrows when the window gets a new size and thus the toolbar
-    // becomes scrollable/unscrollable.
-    WidgetsBinding.instance.addObserver(this);
-
-    // Workaround to allow the scroll controller attach to our ListView so that
-    // we can detect if overflow arrows need to be shown on init.
-    Timer.run(_handleScroll);
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    return LayoutBuilder(
-      builder: (BuildContext context, BoxConstraints constraints) {
-        List<Widget> children = [];
-        double width =
-            (widget.buttons.length + 2) * defaultIconSize * kIconButtonFactor;
-        final isFit = constraints.maxWidth > width;
-        if (!isFit) {
-          children.add(_buildLeftArrow());
-          width = width + 18;
-        }
-
-        children.add(_buildScrollableList(constraints, isFit));
-
-        if (!isFit) {
-          children.add(_buildRightArrow());
-          width = width + 18;
-        }
-
-        return SizedBox(
-          width: min(constraints.maxWidth, width),
-          child: Row(
-            children: children,
-          ),
-        );
-      },
-    );
-  }
-
-  @override
-  void didChangeMetrics() => _handleScroll();
-
-  @override
-  void dispose() {
-    _controller.dispose();
-    WidgetsBinding.instance.removeObserver(this);
-    super.dispose();
-  }
-
-  void _handleScroll() {
-    if (!mounted) return;
-    setState(() {
-      _showLeftArrow =
-          _controller.position.minScrollExtent != _controller.position.pixels;
-      _showRightArrow =
-          _controller.position.maxScrollExtent != _controller.position.pixels;
-    });
-  }
-
-  Widget _buildLeftArrow() {
-    return SizedBox(
-      width: 8,
-      child: Transform.translate(
-        // Move the icon a few pixels to center it
-        offset: const Offset(-5, 0),
-        child: _showLeftArrow ? const Icon(Icons.arrow_left, size: 18) : null,
-      ),
-    );
-  }
-
-  // [[sliver: https://medium.com/flutter/slivers-demystified-6ff68ab0296f]]
-  Widget _buildScrollableList(BoxConstraints constraints, bool isFit) {
-    Widget child = Expanded(
-      child: CustomScrollView(
-        scrollDirection: Axis.horizontal,
-        controller: _controller,
-        physics: const ClampingScrollPhysics(),
-        slivers: [
-          SliverList(
-            delegate: SliverChildBuilderDelegate(
-              (BuildContext context, int index) {
-                return widget.buttons[index];
-              },
-              childCount: widget.buttons.length,
-              addAutomaticKeepAlives: false,
-            ),
-          )
-        ],
-      ),
-    );
-
-    if (!isFit) {
-      child = ScrollConfiguration(
-        // Remove the glowing effect, as we already have the arrow indicators
-        behavior: _NoGlowBehavior(),
-        // The CustomScrollView is necessary so that the children are not
-        // stretched to the height of the toolbar, https://bit.ly/3uC3bjI
-        child: child,
-      );
-    }
-
-    return child;
-  }
-
-  Widget _buildRightArrow() {
-    return SizedBox(
-      width: 8,
-      child: Transform.translate(
-        // Move the icon a few pixels to center it
-        offset: const Offset(-5, 0),
-        child: _showRightArrow ? const Icon(Icons.arrow_right, size: 18) : null,
-      ),
-    );
-  }
-}
-
-class _NoGlowBehavior extends ScrollBehavior {
-  @override
-  Widget buildViewportChrome(BuildContext _, Widget child, AxisDirection __) {
-    return child;
-  }
-}

+ 0 - 38
frontend/app_flowy/lib/plugins/doc/presentation/toolbar/toolbar_icon_button.dart

@@ -1,38 +0,0 @@
-import 'package:flowy_infra/image.dart';
-import 'package:flowy_infra_ui/style_widget/icon_button.dart';
-import 'package:flutter/material.dart';
-import 'package:flowy_infra/theme.dart';
-import 'package:provider/provider.dart';
-
-const double defaultIconSize = 18;
-
-class ToolbarIconButton extends StatelessWidget {
-  final double width;
-  final VoidCallback? onPressed;
-  final bool isToggled;
-  final String iconName;
-  final String tooltipText;
-
-  const ToolbarIconButton({
-    Key? key,
-    required this.onPressed,
-    required this.isToggled,
-    required this.width,
-    required this.iconName,
-    required this.tooltipText,
-  }) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    final theme = context.watch<AppTheme>();
-    return FlowyIconButton(
-      iconPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
-      onPressed: onPressed,
-      width: width,
-      icon: isToggled == true ? svgWidget(iconName, color: Colors.white) : svgWidget(iconName, color: theme.iconColor),
-      fillColor: isToggled == true ? theme.main1 : theme.shader6,
-      hoverColor: isToggled == true ? theme.main1 : theme.hover,
-      tooltipText: tooltipText,
-    );
-  }
-}

+ 0 - 133
frontend/app_flowy/lib/plugins/doc/styles.dart

@@ -1,133 +0,0 @@
-import 'package:app_flowy/plugins/doc/presentation/style_widgets.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_quill/flutter_quill.dart';
-import 'package:provider/provider.dart';
-import 'package:tuple/tuple.dart';
-import 'package:flowy_infra/theme.dart';
-
-DefaultStyles customStyles(BuildContext context) {
-  const baseSpacing = Tuple2<double, double>(6, 0);
-
-  final theme = context.watch<AppTheme>();
-  final themeData = theme.themeData;
-  final fontFamily = makeFontFamily(themeData);
-
-  final defaultTextStyle = DefaultTextStyle.of(context);
-  final baseStyle = defaultTextStyle.style.copyWith(
-    fontSize: 18,
-    height: 1.3,
-    fontWeight: FontWeight.w300,
-    letterSpacing: 0.6,
-    fontFamily: fontFamily,
-  );
-
-  return DefaultStyles(
-      h1: DefaultTextBlockStyle(
-          defaultTextStyle.style.copyWith(
-            fontSize: 34,
-            color: defaultTextStyle.style.color!.withOpacity(0.70),
-            height: 1.15,
-            fontWeight: FontWeight.w300,
-          ),
-          const Tuple2(16, 0),
-          const Tuple2(0, 0),
-          null),
-      h2: DefaultTextBlockStyle(
-          defaultTextStyle.style.copyWith(
-            fontSize: 24,
-            color: defaultTextStyle.style.color!.withOpacity(0.70),
-            height: 1.15,
-            fontWeight: FontWeight.normal,
-          ),
-          const Tuple2(8, 0),
-          const Tuple2(0, 0),
-          null),
-      h3: DefaultTextBlockStyle(
-          defaultTextStyle.style.copyWith(
-            fontSize: 20,
-            color: defaultTextStyle.style.color!.withOpacity(0.70),
-            height: 1.25,
-            fontWeight: FontWeight.w500,
-          ),
-          const Tuple2(8, 0),
-          const Tuple2(0, 0),
-          null),
-      paragraph: DefaultTextBlockStyle(
-          baseStyle, const Tuple2(10, 0), const Tuple2(0, 0), null),
-      bold: const TextStyle(fontWeight: FontWeight.bold),
-      italic: const TextStyle(fontStyle: FontStyle.italic),
-      small: const TextStyle(fontSize: 12, color: Colors.black45),
-      underline: const TextStyle(decoration: TextDecoration.underline),
-      strikeThrough: const TextStyle(decoration: TextDecoration.lineThrough),
-      inlineCode: TextStyle(
-        color: Colors.blue.shade900.withOpacity(0.9),
-        fontFamily: fontFamily,
-        fontSize: 13,
-      ),
-      link: TextStyle(
-        color: themeData.colorScheme.secondary,
-        decoration: TextDecoration.underline,
-      ),
-      color: theme.textColor,
-      placeHolder: DefaultTextBlockStyle(
-          defaultTextStyle.style.copyWith(
-            fontSize: 20,
-            height: 1.5,
-            color: Colors.grey.withOpacity(0.6),
-          ),
-          const Tuple2(0, 0),
-          const Tuple2(0, 0),
-          null),
-      lists: DefaultListBlockStyle(baseStyle, baseSpacing, const Tuple2(0, 6),
-          null, StyleWidgetBuilder.checkbox(theme)),
-      quote: DefaultTextBlockStyle(
-          TextStyle(color: baseStyle.color!.withOpacity(0.6)),
-          baseSpacing,
-          const Tuple2(6, 2),
-          BoxDecoration(
-            border: Border(
-              left: BorderSide(width: 4, color: theme.shader5),
-            ),
-          )),
-      code: DefaultTextBlockStyle(
-          TextStyle(
-            color: Colors.blue.shade900.withOpacity(0.9),
-            fontFamily: fontFamily,
-            fontSize: 13,
-            height: 1.15,
-          ),
-          baseSpacing,
-          const Tuple2(0, 0),
-          BoxDecoration(
-            color: Colors.grey.shade50,
-            borderRadius: BorderRadius.circular(2),
-          )),
-      indent: DefaultTextBlockStyle(
-          baseStyle, baseSpacing, const Tuple2(0, 6), null),
-      align: DefaultTextBlockStyle(
-          baseStyle, const Tuple2(0, 0), const Tuple2(0, 0), null),
-      leading: DefaultTextBlockStyle(
-          baseStyle, const Tuple2(0, 0), const Tuple2(0, 0), null),
-      sizeSmall: const TextStyle(fontSize: 10),
-      sizeLarge: const TextStyle(fontSize: 18),
-      sizeHuge: const TextStyle(fontSize: 22));
-}
-
-String makeFontFamily(ThemeData themeData) {
-  String fontFamily;
-  switch (themeData.platform) {
-    case TargetPlatform.iOS:
-    case TargetPlatform.macOS:
-      fontFamily = 'Mulish';
-      break;
-    case TargetPlatform.android:
-    case TargetPlatform.fuchsia:
-    case TargetPlatform.windows:
-    case TargetPlatform.linux:
-      fontFamily = 'Roboto Mono';
-      break;
-    default:
-      throw UnimplementedError();
-  }
-  return fontFamily;
-}

+ 1 - 3
frontend/app_flowy/lib/user/presentation/router.dart

@@ -12,9 +12,7 @@ import 'package:flowy_sdk/protobuf/flowy-folder/protobuf.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 
 
 class AuthRouter {
 class AuthRouter {
-  void pushForgetPasswordScreen(BuildContext context) {
-    // TODO: implement showForgetPasswordScreen
-  }
+  void pushForgetPasswordScreen(BuildContext context) {}
 
 
   void pushWelcomeScreen(BuildContext context, UserProfilePB userProfile) {
   void pushWelcomeScreen(BuildContext context, UserProfilePB userProfile) {
     getIt<SplashRoute>().pushWelcomeScreen(context, userProfile);
     getIt<SplashRoute>().pushWelcomeScreen(context, userProfile);

+ 0 - 30
frontend/app_flowy/lib/workspace/application/markdown/delta_markdown.dart

@@ -1,30 +0,0 @@
-library delta_markdown;
-
-import 'dart:convert';
-
-import 'src/delta_markdown_decoder.dart';
-import 'src/delta_markdown_encoder.dart';
-import 'src/version.dart';
-
-const version = packageVersion;
-
-/// Codec used to convert between Markdown and Quill deltas.
-const DeltaMarkdownCodec _kCodec = DeltaMarkdownCodec();
-
-String markdownToDelta(String markdown) {
-  return _kCodec.decode(markdown);
-}
-
-String deltaToMarkdown(String delta) {
-  return _kCodec.encode(delta);
-}
-
-class DeltaMarkdownCodec extends Codec<String, String> {
-  const DeltaMarkdownCodec();
-
-  @override
-  Converter<String, String> get decoder => DeltaMarkdownDecoder();
-
-  @override
-  Converter<String, String> get encoder => DeltaMarkdownEncoder();
-}

+ 0 - 113
frontend/app_flowy/lib/workspace/application/markdown/src/ast.dart

@@ -1,113 +0,0 @@
-// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-typedef Resolver = Node? Function(String name, [String? title]);
-
-/// Base class for any AST item.
-///
-/// Roughly corresponds to Node in the DOM. Will be either an Element or Text.
-class Node {
-  void accept(NodeVisitor visitor) {}
-
-  bool isToplevel = false;
-
-  String? get textContent {
-    return null;
-  }
-}
-
-/// A named tag that can contain other nodes.
-class Element extends Node {
-  /// Instantiates a [tag] Element with [children].
-  Element(this.tag, this.children) : attributes = <String, String>{};
-
-  /// Instantiates an empty, self-closing [tag] Element.
-  Element.empty(this.tag)
-      : children = null,
-        attributes = {};
-
-  /// Instantiates a [tag] Element with no [children].
-  Element.withTag(this.tag)
-      : children = [],
-        attributes = {};
-
-  /// Instantiates a [tag] Element with a single Text child.
-  Element.text(this.tag, String text)
-      : children = [Text(text)],
-        attributes = {};
-
-  final String tag;
-  final List<Node>? children;
-  final Map<String, String> attributes;
-  String? generatedId;
-
-  /// Whether this element is self-closing.
-  bool get isEmpty => children == null;
-
-  @override
-  void accept(NodeVisitor visitor) {
-    if (visitor.visitElementBefore(this)) {
-      if (children != null) {
-        for (final child in children!) {
-          child.accept(visitor);
-        }
-      }
-      visitor.visitElementAfter(this);
-    }
-  }
-
-  @override
-  String get textContent => children == null
-      ? ''
-      : children!.map((child) => child.textContent).join();
-}
-
-/// A plain text element.
-class Text extends Node {
-  Text(this.text);
-
-  final String text;
-
-  @override
-  void accept(NodeVisitor visitor) => visitor.visitText(this);
-
-  @override
-  String get textContent => text;
-}
-
-/// Inline content that has not been parsed into inline nodes (strong, links,
-/// etc).
-///
-/// These placeholder nodes should only remain in place while the block nodes
-/// of a document are still being parsed, in order to gather all reference link
-/// definitions.
-class UnparsedContent extends Node {
-  UnparsedContent(this.textContent);
-
-  @override
-  final String textContent;
-
-  @override
-  void accept(NodeVisitor visitor);
-}
-
-/// Visitor pattern for the AST.
-///
-/// Renderers or other AST transformers should implement this.
-abstract class NodeVisitor {
-  /// Called when a Text node has been reached.
-  void visitText(Text text);
-
-  /// Called when an Element has been reached, before its children have been
-  /// visited.
-  ///
-  /// Returns `false` to skip its children.
-  bool visitElementBefore(Element element);
-
-  /// Called when an Element has been reached, after its children have been
-  /// visited.
-  ///
-  /// Will not be called if [visitElementBefore] returns `false`.
-  void visitElementAfter(Element element);
-}

+ 0 - 1096
frontend/app_flowy/lib/workspace/application/markdown/src/block_parser.dart

@@ -1,1096 +0,0 @@
-// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'ast.dart';
-import 'document.dart';
-import 'util.dart';
-
-/// The line contains only whitespace or is empty.
-final _emptyPattern = RegExp(r'^(?:[ \t]*)$');
-
-/// A series of `=` or `-` (on the next line) define setext-style headers.
-final _setextPattern = RegExp(r'^[ ]{0,3}(=+|-+)\s*$');
-
-/// Leading (and trailing) `#` define atx-style headers.
-///
-/// Starts with 1-6 unescaped `#` characters which must not be followed by a
-/// non-space character. Line may end with any number of `#` characters,.
-final _headerPattern = RegExp(r'^ {0,3}(#{1,6})[ \x09\x0b\x0c](.*?)#*$');
-
-/// The line starts with `>` with one optional space after.
-final _blockquotePattern = RegExp(r'^[ ]{0,3}>[ ]?(.*)$');
-
-/// A line indented four spaces. Used for code blocks and lists.
-final _indentPattern = RegExp(r'^(?:    | {0,3}\t)(.*)$');
-
-/// Fenced code block.
-final _codePattern = RegExp(r'^[ ]{0,3}(`{3,}|~{3,})(.*)$');
-
-/// Three or more hyphens, asterisks or underscores by themselves. Note that
-/// a line like `----` is valid as both HR and SETEXT. In case of a tie,
-/// SETEXT should win.
-final _hrPattern = RegExp(r'^ {0,3}([-*_])[ \t]*\1[ \t]*\1(?:\1|[ \t])*$');
-
-/// One or more whitespace, for compressing.
-final _oneOrMoreWhitespacePattern = RegExp('[ \n\r\t]+');
-
-/// A line starting with one of these markers: `-`, `*`, `+`. May have up to
-/// three leading spaces before the marker and any number of spaces or tabs
-/// after.
-///
-/// Contains a dummy group at [2], so that the groups in [_ulPattern] and
-/// [_olPattern] match up; in both, [2] is the length of the number that begins
-/// the list marker.
-final _ulPattern = RegExp(r'^([ ]{0,3})()([*+-])(([ \t])([ \t]*)(.*))?$');
-
-/// A line starting with a number like `123.`. May have up to three leading
-/// spaces before the marker and any number of spaces or tabs after.
-final _olPattern =
-    RegExp(r'^([ ]{0,3})(\d{1,9})([\.)])(([ \t])([ \t]*)(.*))?$');
-
-/// A line of hyphens separated by at least one pipe.
-final _tablePattern = RegExp(r'^[ ]{0,3}\|?( *:?\-+:? *\|)+( *:?\-+:? *)?$');
-
-/// Maintains the internal state needed to parse a series of lines into blocks
-/// of Markdown suitable for further inline parsing.
-class BlockParser {
-  BlockParser(this.lines, this.document) {
-    blockSyntaxes
-      ..addAll(document.blockSyntaxes)
-      ..addAll(standardBlockSyntaxes);
-  }
-
-  final List<String> lines;
-
-  /// The Markdown document this parser is parsing.
-  final Document document;
-
-  /// The enabled block syntaxes.
-  ///
-  /// To turn a series of lines into blocks, each of these will be tried in
-  /// turn. Order matters here.
-  final List<BlockSyntax> blockSyntaxes = [];
-
-  /// Index of the current line.
-  int _pos = 0;
-
-  /// Whether the parser has encountered a blank line between two block-level
-  /// elements.
-  bool encounteredBlankLine = false;
-
-  /// The collection of built-in block parsers.
-  final List<BlockSyntax> standardBlockSyntaxes = [
-    const EmptyBlockSyntax(),
-    const BlockTagBlockHtmlSyntax(),
-    LongBlockHtmlSyntax(r'^ {0,3}<pre(?:\s|>|$)', '</pre>'),
-    LongBlockHtmlSyntax(r'^ {0,3}<script(?:\s|>|$)', '</script>'),
-    LongBlockHtmlSyntax(r'^ {0,3}<style(?:\s|>|$)', '</style>'),
-    LongBlockHtmlSyntax('^ {0,3}<!--', '-->'),
-    LongBlockHtmlSyntax('^ {0,3}<\\?', '\\?>'),
-    LongBlockHtmlSyntax('^ {0,3}<![A-Z]', '>'),
-    LongBlockHtmlSyntax('^ {0,3}<!\\[CDATA\\[', '\\]\\]>'),
-    const OtherTagBlockHtmlSyntax(),
-    const SetextHeaderSyntax(),
-    const HeaderSyntax(),
-    const CodeBlockSyntax(),
-    const BlockquoteSyntax(),
-    const HorizontalRuleSyntax(),
-    const UnorderedListSyntax(),
-    const OrderedListSyntax(),
-    const ParagraphSyntax()
-  ];
-
-  /// Gets the current line.
-  String get current => lines[_pos];
-
-  /// Gets the line after the current one or `null` if there is none.
-  String? get next {
-    // Don't read past the end.
-    if (_pos >= lines.length - 1) {
-      return null;
-    }
-    return lines[_pos + 1];
-  }
-
-  /// Gets the line that is [linesAhead] lines ahead of the current one, or
-  /// `null` if there is none.
-  ///
-  /// `peek(0)` is equivalent to [current].
-  ///
-  /// `peek(1)` is equivalent to [next].
-  String? peek(int linesAhead) {
-    if (linesAhead < 0) {
-      throw ArgumentError('Invalid linesAhead: $linesAhead; must be >= 0.');
-    }
-    // Don't read past the end.
-    if (_pos >= lines.length - linesAhead) {
-      return null;
-    }
-    return lines[_pos + linesAhead];
-  }
-
-  void advance() {
-    _pos++;
-  }
-
-  bool get isDone => _pos >= lines.length;
-
-  /// Gets whether or not the current line matches the given pattern.
-  bool matches(RegExp regex) {
-    if (isDone) {
-      return false;
-    }
-    return regex.firstMatch(current) != null;
-  }
-
-  /// Gets whether or not the next line matches the given pattern.
-  bool matchesNext(RegExp regex) {
-    if (next == null) {
-      return false;
-    }
-    return regex.firstMatch(next!) != null;
-  }
-
-  List<Node> parseLines() {
-    final blocks = <Node>[];
-    while (!isDone) {
-      for (final syntax in blockSyntaxes) {
-        if (syntax.canParse(this)) {
-          final block = syntax.parse(this);
-          if (block != null) {
-            blocks.add(block);
-          }
-          break;
-        }
-      }
-    }
-
-    return blocks;
-  }
-}
-
-abstract class BlockSyntax {
-  const BlockSyntax();
-
-  /// Gets the regex used to identify the beginning of this block, if any.
-  RegExp? get pattern => null;
-
-  bool get canEndBlock => true;
-
-  bool canParse(BlockParser parser) {
-    return pattern!.firstMatch(parser.current) != null;
-  }
-
-  Node? parse(BlockParser parser);
-
-  List<String?> parseChildLines(BlockParser parser) {
-    // Grab all of the lines that form the block element.
-    final childLines = <String?>[];
-
-    while (!parser.isDone) {
-      final match = pattern!.firstMatch(parser.current);
-      if (match == null) {
-        break;
-      }
-      childLines.add(match[1]);
-      parser.advance();
-    }
-
-    return childLines;
-  }
-
-  /// Gets whether or not [parser]'s current line should end the previous block.
-  static bool isAtBlockEnd(BlockParser parser) {
-    if (parser.isDone) {
-      return true;
-    }
-    return parser.blockSyntaxes.any((s) => s.canParse(parser) && s.canEndBlock);
-  }
-
-  /// Generates a valid HTML anchor from the inner text of [element].
-  static String generateAnchorHash(Element element) =>
-      element.children!.first.textContent!
-          .toLowerCase()
-          .trim()
-          .replaceAll(RegExp(r'[^a-z0-9 _-]'), '')
-          .replaceAll(RegExp(r'\s'), '-');
-}
-
-class EmptyBlockSyntax extends BlockSyntax {
-  const EmptyBlockSyntax();
-
-  @override
-  RegExp get pattern => _emptyPattern;
-
-  @override
-  Node? parse(BlockParser parser) {
-    parser
-      ..encounteredBlankLine = true
-      ..advance();
-
-    // Don't actually emit anything.
-    return null;
-  }
-}
-
-/// Parses setext-style headers.
-class SetextHeaderSyntax extends BlockSyntax {
-  const SetextHeaderSyntax();
-
-  @override
-  bool canParse(BlockParser parser) {
-    if (!_interperableAsParagraph(parser.current)) {
-      return false;
-    }
-
-    var i = 1;
-    while (true) {
-      final nextLine = parser.peek(i);
-      if (nextLine == null) {
-        // We never reached an underline.
-        return false;
-      }
-      if (_setextPattern.hasMatch(nextLine)) {
-        return true;
-      }
-      // Ensure that we're still in something like paragraph text.
-      if (!_interperableAsParagraph(nextLine)) {
-        return false;
-      }
-      i++;
-    }
-  }
-
-  @override
-  Node parse(BlockParser parser) {
-    final lines = <String>[];
-    late String tag;
-    while (!parser.isDone) {
-      final match = _setextPattern.firstMatch(parser.current);
-      if (match == null) {
-        // More text.
-        lines.add(parser.current);
-        parser.advance();
-        continue;
-      } else {
-        // The underline.
-        tag = (match[1]![0] == '=') ? 'h1' : 'h2';
-        parser.advance();
-        break;
-      }
-    }
-
-    final contents = UnparsedContent(lines.join('\n'));
-
-    return Element(tag, [contents]);
-  }
-
-  bool _interperableAsParagraph(String line) =>
-      !(_indentPattern.hasMatch(line) ||
-          _codePattern.hasMatch(line) ||
-          _headerPattern.hasMatch(line) ||
-          _blockquotePattern.hasMatch(line) ||
-          _hrPattern.hasMatch(line) ||
-          _ulPattern.hasMatch(line) ||
-          _olPattern.hasMatch(line) ||
-          _emptyPattern.hasMatch(line));
-}
-
-/// Parses setext-style headers, and adds generated IDs to the generated
-/// elements.
-class SetextHeaderWithIdSyntax extends SetextHeaderSyntax {
-  const SetextHeaderWithIdSyntax();
-
-  @override
-  Node parse(BlockParser parser) {
-    final element = super.parse(parser) as Element;
-    element.generatedId = BlockSyntax.generateAnchorHash(element);
-    return element;
-  }
-}
-
-/// Parses atx-style headers: `## Header ##`.
-class HeaderSyntax extends BlockSyntax {
-  const HeaderSyntax();
-
-  @override
-  RegExp get pattern => _headerPattern;
-
-  @override
-  Node parse(BlockParser parser) {
-    final match = pattern.firstMatch(parser.current)!;
-    parser.advance();
-    final level = match[1]!.length;
-    final contents = UnparsedContent(match[2]!.trim());
-    return Element('h$level', [contents]);
-  }
-}
-
-/// Parses atx-style headers, and adds generated IDs to the generated elements.
-class HeaderWithIdSyntax extends HeaderSyntax {
-  const HeaderWithIdSyntax();
-
-  @override
-  Node parse(BlockParser parser) {
-    final element = super.parse(parser) as Element;
-    element.generatedId = BlockSyntax.generateAnchorHash(element);
-    return element;
-  }
-}
-
-/// Parses email-style blockquotes: `> quote`.
-class BlockquoteSyntax extends BlockSyntax {
-  const BlockquoteSyntax();
-
-  @override
-  RegExp get pattern => _blockquotePattern;
-
-  @override
-  List<String> parseChildLines(BlockParser parser) {
-    // Grab all of the lines that form the blockquote, stripping off the ">".
-    final childLines = <String>[];
-
-    while (!parser.isDone) {
-      final match = pattern.firstMatch(parser.current);
-      if (match != null) {
-        childLines.add(match[1]!);
-        parser.advance();
-        continue;
-      }
-
-      // A paragraph continuation is OK. This is content that cannot be parsed
-      // as any other syntax except Paragraph, and it doesn't match the bar in
-      // a Setext header.
-      if (parser.blockSyntaxes.firstWhere((s) => s.canParse(parser))
-          is ParagraphSyntax) {
-        childLines.add(parser.current);
-        parser.advance();
-      } else {
-        break;
-      }
-    }
-
-    return childLines;
-  }
-
-  @override
-  Node parse(BlockParser parser) {
-    final childLines = parseChildLines(parser);
-
-    // Recursively parse the contents of the blockquote.
-    final children = BlockParser(childLines, parser.document).parseLines();
-    return Element('blockquote', children);
-  }
-}
-
-/// Parses preformatted code blocks that are indented four spaces.
-class CodeBlockSyntax extends BlockSyntax {
-  const CodeBlockSyntax();
-
-  @override
-  RegExp get pattern => _indentPattern;
-
-  @override
-  bool get canEndBlock => false;
-
-  @override
-  List<String?> parseChildLines(BlockParser parser) {
-    final childLines = <String?>[];
-
-    while (!parser.isDone) {
-      final match = pattern.firstMatch(parser.current);
-      if (match != null) {
-        childLines.add(match[1]);
-        parser.advance();
-      } else {
-        // If there's a codeblock, then a newline, then a codeblock, keep the
-        // code blocks together.
-        final nextMatch =
-            parser.next != null ? pattern.firstMatch(parser.next!) : null;
-        if (parser.current.trim() == '' && nextMatch != null) {
-          childLines..add('')..add(nextMatch[1]);
-          parser..advance()..advance();
-        } else {
-          break;
-        }
-      }
-    }
-    return childLines;
-  }
-
-  @override
-  Node parse(BlockParser parser) {
-    final childLines = parseChildLines(parser)
-      // The Markdown tests expect a trailing newline.
-      ..add('');
-
-    // Escape the code.
-    final escaped = escapeHtml(childLines.join('\n'));
-
-    return Element('pre', [Element.text('code', escaped)]);
-  }
-}
-
-/// Parses preformatted code blocks between two ~~~ or ``` sequences.
-///
-/// See [Pandoc's documentation](http://pandoc.org/README.html#fenced-code-blocks).
-class FencedCodeBlockSyntax extends BlockSyntax {
-  const FencedCodeBlockSyntax();
-
-  @override
-  RegExp get pattern => _codePattern;
-
-  @override
-  List<String> parseChildLines(BlockParser parser, [String? endBlock]) {
-    endBlock ??= '';
-
-    final childLines = <String>[];
-    parser.advance();
-
-    while (!parser.isDone) {
-      final match = pattern.firstMatch(parser.current);
-      if (match == null || !match[1]!.startsWith(endBlock)) {
-        childLines.add(parser.current);
-        parser.advance();
-      } else {
-        parser.advance();
-        break;
-      }
-    }
-
-    return childLines;
-  }
-
-  @override
-  Node parse(BlockParser parser) {
-    // Get the syntax identifier, if there is one.
-    final match = pattern.firstMatch(parser.current)!;
-    final endBlock = match.group(1);
-    var infoString = match.group(2)!;
-
-    final childLines = parseChildLines(parser, endBlock)
-      // The Markdown tests expect a trailing newline.
-      ..add('');
-
-    final code = Element.text('code', childLines.join('\n'));
-
-    // the info-string should be trimmed
-    // http://spec.commonmark.org/0.22/#example-100
-    infoString = infoString.trim();
-    if (infoString.isNotEmpty) {
-      // only use the first word in the syntax
-      // http://spec.commonmark.org/0.22/#example-100
-      infoString = infoString.split(' ').first;
-      code.attributes['class'] = 'language-$infoString';
-    }
-
-    final element = Element('pre', [code]);
-    return element;
-  }
-}
-
-/// Parses horizontal rules like `---`, `_ _ _`, `*  *  *`, etc.
-class HorizontalRuleSyntax extends BlockSyntax {
-  const HorizontalRuleSyntax();
-
-  @override
-  RegExp get pattern => _hrPattern;
-
-  @override
-  Node parse(BlockParser parser) {
-    parser.advance();
-    return Element.empty('hr');
-  }
-}
-
-/// Parses inline HTML at the block level. This differs from other Markdown
-/// implementations in several ways:
-///
-/// 1.  This one is way way WAY simpler.
-/// 2.  Essentially no HTML parsing or validation is done. We're a Markdown
-///     parser, not an HTML parser!
-abstract class BlockHtmlSyntax extends BlockSyntax {
-  const BlockHtmlSyntax();
-
-  @override
-  bool get canEndBlock => true;
-}
-
-class BlockTagBlockHtmlSyntax extends BlockHtmlSyntax {
-  const BlockTagBlockHtmlSyntax();
-
-  static final _pattern = RegExp(
-      r'^ {0,3}</?(?:address|article|aside|base|basefont|blockquote|body|'
-      r'caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|'
-      r'figcaption|figure|footer|form|frame|frameset|h1|head|header|hr|html|'
-      r'iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|'
-      r'option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|'
-      'title|tr|track|ul)'
-      r'(?:\s|>|/>|$)');
-
-  @override
-  RegExp get pattern => _pattern;
-
-  @override
-  Node parse(BlockParser parser) {
-    final childLines = <String>[];
-
-    // Eat until we hit a blank line.
-    while (!parser.isDone && !parser.matches(_emptyPattern)) {
-      childLines.add(parser.current);
-      parser.advance();
-    }
-
-    return Text(childLines.join('\n'));
-  }
-}
-
-class OtherTagBlockHtmlSyntax extends BlockTagBlockHtmlSyntax {
-  const OtherTagBlockHtmlSyntax();
-
-  @override
-  bool get canEndBlock => false;
-
-  // Really hacky way to detect "other" HTML. This matches:
-  //
-  // * any opening spaces
-  // * open bracket and maybe a slash ("<" or "</")
-  // * some word characters
-  // * either:
-  //   * a close bracket, or
-  //   * whitespace followed by not-brackets followed by a close bracket
-  // * possible whitespace and the end of the line.
-  @override
-  RegExp get pattern => RegExp(r'^ {0,3}</?\w+(?:>|\s+[^>]*>)\s*$');
-}
-
-/// A BlockHtmlSyntax that has a specific `endPattern`.
-///
-/// In practice this means that the syntax dominates; it is allowed to eat
-/// many lines, including blank lines, before matching its `endPattern`.
-class LongBlockHtmlSyntax extends BlockHtmlSyntax {
-  LongBlockHtmlSyntax(String pattern, String endPattern)
-      : pattern = RegExp(pattern),
-        _endPattern = RegExp(endPattern);
-
-  @override
-  final RegExp pattern;
-  final RegExp _endPattern;
-
-  @override
-  Node parse(BlockParser parser) {
-    final childLines = <String>[];
-    // Eat until we hit [endPattern].
-    while (!parser.isDone) {
-      childLines.add(parser.current);
-      if (parser.matches(_endPattern)) {
-        break;
-      }
-      parser.advance();
-    }
-
-    parser.advance();
-    return Text(childLines.join('\n'));
-  }
-}
-
-class ListItem {
-  ListItem(this.lines);
-
-  bool forceBlock = false;
-  final List<String> lines;
-}
-
-/// Base class for both ordered and unordered lists.
-abstract class ListSyntax extends BlockSyntax {
-  const ListSyntax();
-
-  @override
-  bool get canEndBlock => true;
-
-  String get listTag;
-
-  /// A list of patterns that can start a valid block within a list item.
-  static final blocksInList = [
-    _blockquotePattern,
-    _headerPattern,
-    _hrPattern,
-    _indentPattern,
-    _ulPattern,
-    _olPattern
-  ];
-
-  static final _whitespaceRe = RegExp('[ \t]*');
-
-  @override
-  Node parse(BlockParser parser) {
-    final items = <ListItem>[];
-    var childLines = <String>[];
-
-    void endItem() {
-      if (childLines.isNotEmpty) {
-        items.add(ListItem(childLines));
-        childLines = <String>[];
-      }
-    }
-
-    Match? match;
-    bool tryMatch(RegExp pattern) {
-      match = pattern.firstMatch(parser.current);
-      return match != null;
-    }
-
-    String? listMarker;
-    String? indent;
-    // In case the first number in an ordered list is not 1, use it as the
-    // "start".
-    int? startNumber;
-
-    while (!parser.isDone) {
-      final leadingSpace =
-          _whitespaceRe.matchAsPrefix(parser.current)!.group(0)!;
-      final leadingExpandedTabLength = _expandedTabLength(leadingSpace);
-      if (tryMatch(_emptyPattern)) {
-        if (_emptyPattern.firstMatch(parser.next ?? '') != null) {
-          // Two blank lines ends a list.
-          break;
-        }
-        // Add a blank line to the current list item.
-        childLines.add('');
-      } else if (indent != null && indent.length <= leadingExpandedTabLength) {
-        // Strip off indent and add to current item.
-        final line = parser.current
-            .replaceFirst(leadingSpace, ' ' * leadingExpandedTabLength)
-            .replaceFirst(indent, '');
-        childLines.add(line);
-      } else if (tryMatch(_hrPattern)) {
-        // Horizontal rule takes precedence to a list item.
-        break;
-      } else if (tryMatch(_ulPattern) || tryMatch(_olPattern)) {
-        final precedingWhitespace = match![1];
-        final digits = match![2] ?? '';
-        if (startNumber == null && digits.isNotEmpty) {
-          startNumber = int.parse(digits);
-        }
-        final marker = match![3];
-        final firstWhitespace = match![5] ?? '';
-        final restWhitespace = match![6] ?? '';
-        final content = match![7] ?? '';
-        final isBlank = content.isEmpty;
-        if (listMarker != null && listMarker != marker) {
-          // Changing the bullet or ordered list delimiter starts a list.
-          break;
-        }
-        listMarker = marker;
-        final markerAsSpaces = ' ' * (digits.length + marker!.length);
-        if (isBlank) {
-          // See http://spec.commonmark.org/0.28/#list-items under "3. Item
-          // starting with a blank line."
-          //
-          // If the list item starts with a blank line, the final piece of the
-          // indentation is just a single space.
-          indent = '$precedingWhitespace$markerAsSpaces ';
-        } else if (restWhitespace.length >= 4) {
-          // See http://spec.commonmark.org/0.28/#list-items under "2. Item
-          // starting with indented code."
-          //
-          // If the list item starts with indented code, we need to _not_ count
-          // any indentation past the required whitespace character.
-          indent = precedingWhitespace! + markerAsSpaces + firstWhitespace;
-        } else {
-          indent = precedingWhitespace! +
-              markerAsSpaces +
-              firstWhitespace +
-              restWhitespace;
-        }
-        // End the current list item and start a one.
-        endItem();
-        childLines.add(restWhitespace + content);
-      } else if (BlockSyntax.isAtBlockEnd(parser)) {
-        // Done with the list.
-        break;
-      } else {
-        // If the previous item is a blank line, this means we're done with the
-        // list and are starting a top-level paragraph.
-        if ((childLines.isNotEmpty) && (childLines.last == '')) {
-          parser.encounteredBlankLine = true;
-          break;
-        }
-
-        // Anything else is paragraph continuation text.
-        childLines.add(parser.current);
-      }
-      parser.advance();
-    }
-
-    endItem();
-    final itemNodes = <Element>[];
-
-    items.forEach(removeLeadingEmptyLine);
-    final anyEmptyLines = removeTrailingEmptyLines(items);
-    var anyEmptyLinesBetweenBlocks = false;
-
-    for (final item in items) {
-      final itemParser = BlockParser(item.lines, parser.document);
-      final children = itemParser.parseLines();
-      itemNodes.add(Element('li', children));
-      anyEmptyLinesBetweenBlocks =
-          anyEmptyLinesBetweenBlocks || itemParser.encounteredBlankLine;
-    }
-
-    // Must strip paragraph tags if the list is "tight".
-    // http://spec.commonmark.org/0.28/#lists
-    final listIsTight = !anyEmptyLines && !anyEmptyLinesBetweenBlocks;
-
-    if (listIsTight) {
-      // We must post-process the list items, converting any top-level paragraph
-      // elements to just text elements.
-      for (final item in itemNodes) {
-        for (var i = 0; i < item.children!.length; i++) {
-          final child = item.children![i];
-          if (child is Element && child.tag == 'p') {
-            item.children!.removeAt(i);
-            item.children!.insertAll(i, child.children!);
-          }
-        }
-      }
-    }
-
-    if (listTag == 'ol' && startNumber != 1) {
-      return Element(listTag, itemNodes)..attributes['start'] = '$startNumber';
-    } else {
-      return Element(listTag, itemNodes);
-    }
-  }
-
-  void removeLeadingEmptyLine(ListItem item) {
-    if (item.lines.isNotEmpty && _emptyPattern.hasMatch(item.lines.first)) {
-      item.lines.removeAt(0);
-    }
-  }
-
-  /// Removes any trailing empty lines and notes whether any items are separated
-  /// by such lines.
-  bool removeTrailingEmptyLines(List<ListItem> items) {
-    var anyEmpty = false;
-    for (var i = 0; i < items.length; i++) {
-      if (items[i].lines.length == 1) {
-        continue;
-      }
-      while (items[i].lines.isNotEmpty &&
-          _emptyPattern.hasMatch(items[i].lines.last)) {
-        if (i < items.length - 1) {
-          anyEmpty = true;
-        }
-        items[i].lines.removeLast();
-      }
-    }
-    return anyEmpty;
-  }
-
-  static int _expandedTabLength(String input) {
-    var length = 0;
-    for (final char in input.codeUnits) {
-      length += char == 0x9 ? 4 - (length % 4) : 1;
-    }
-    return length;
-  }
-}
-
-/// Parses unordered lists.
-class UnorderedListSyntax extends ListSyntax {
-  const UnorderedListSyntax();
-
-  @override
-  RegExp get pattern => _ulPattern;
-
-  @override
-  String get listTag => 'ul';
-}
-
-/// Parses ordered lists.
-class OrderedListSyntax extends ListSyntax {
-  const OrderedListSyntax();
-
-  @override
-  RegExp get pattern => _olPattern;
-
-  @override
-  String get listTag => 'ol';
-}
-
-/// Parses tables.
-class TableSyntax extends BlockSyntax {
-  const TableSyntax();
-
-  static final _pipePattern = RegExp(r'\s*\|\s*');
-  static final _openingPipe = RegExp(r'^\|\s*');
-  static final _closingPipe = RegExp(r'\s*\|$');
-
-  @override
-  bool get canEndBlock => false;
-
-  @override
-  bool canParse(BlockParser parser) {
-    // Note: matches *next* line, not the current one. We're looking for the
-    // bar separating the head row from the body rows.
-    return parser.matchesNext(_tablePattern);
-  }
-
-  /// Parses a table into its three parts:
-  ///
-  /// * a head row of head cells (`<th>` cells)
-  /// * a divider of hyphens and pipes (not rendered)
-  /// * many body rows of body cells (`<td>` cells)
-  @override
-  Node? parse(BlockParser parser) {
-    final alignments = parseAlignments(parser.next!);
-    final columnCount = alignments.length;
-    final headRow = parseRow(parser, alignments, 'th');
-    if (headRow.children!.length != columnCount) {
-      return null;
-    }
-    final head = Element('thead', [headRow]);
-
-    // Advance past the divider of hyphens.
-    parser.advance();
-
-    final rows = <Element>[];
-    while (!parser.isDone && !BlockSyntax.isAtBlockEnd(parser)) {
-      final row = parseRow(parser, alignments, 'td');
-      while (row.children!.length < columnCount) {
-        // Insert synthetic empty cells.
-        row.children!.add(Element.empty('td'));
-      }
-      while (row.children!.length > columnCount) {
-        row.children!.removeLast();
-      }
-      rows.add(row);
-    }
-    if (rows.isEmpty) {
-      return Element('table', [head]);
-    } else {
-      final body = Element('tbody', rows);
-
-      return Element('table', [head, body]);
-    }
-  }
-
-  List<String?> parseAlignments(String line) {
-    line = line.replaceFirst(_openingPipe, '').replaceFirst(_closingPipe, '');
-    return line.split('|').map((column) {
-      column = column.trim();
-      if (column.startsWith(':') && column.endsWith(':')) {
-        return 'center';
-      }
-      if (column.startsWith(':')) {
-        return 'left';
-      }
-      if (column.endsWith(':')) {
-        return 'right';
-      }
-      return null;
-    }).toList();
-  }
-
-  Element parseRow(
-      BlockParser parser, List<String?> alignments, String cellType) {
-    final line = parser.current
-        .replaceFirst(_openingPipe, '')
-        .replaceFirst(_closingPipe, '');
-    final cells = line.split(_pipePattern);
-    parser.advance();
-    final row = <Element>[];
-    String? preCell;
-
-    for (var cell in cells) {
-      if (preCell != null) {
-        cell = preCell + cell;
-        preCell = null;
-      }
-      if (cell.endsWith('\\')) {
-        preCell = '${cell.substring(0, cell.length - 1)}|';
-        continue;
-      }
-
-      final contents = UnparsedContent(cell);
-      row.add(Element(cellType, [contents]));
-    }
-
-    for (var i = 0; i < row.length && i < alignments.length; i++) {
-      if (alignments[i] == null) {
-        continue;
-      }
-      row[i].attributes['style'] = 'text-align: ${alignments[i]};';
-    }
-
-    return Element('tr', row);
-  }
-}
-
-/// Parses paragraphs of regular text.
-class ParagraphSyntax extends BlockSyntax {
-  const ParagraphSyntax();
-
-  static final _reflinkDefinitionStart = RegExp(r'[ ]{0,3}\[');
-
-  static final _whitespacePattern = RegExp(r'^\s*$');
-
-  @override
-  bool get canEndBlock => false;
-
-  @override
-  bool canParse(BlockParser parser) => true;
-
-  @override
-  Node parse(BlockParser parser) {
-    final childLines = <String>[];
-
-    // Eat until we hit something that ends a paragraph.
-    while (!BlockSyntax.isAtBlockEnd(parser)) {
-      childLines.add(parser.current);
-      parser.advance();
-    }
-
-    final paragraphLines = _extractReflinkDefinitions(parser, childLines);
-    if (paragraphLines == null) {
-      // Paragraph consisted solely of reference link definitions.
-      return Text('');
-    } else {
-      final contents = UnparsedContent(paragraphLines.join('\n'));
-      return Element('p', [contents]);
-    }
-  }
-
-  /// Extract reference link definitions from the front of the paragraph, and
-  /// return the remaining paragraph lines.
-  List<String>? _extractReflinkDefinitions(
-      BlockParser parser, List<String> lines) {
-    bool lineStartsReflinkDefinition(int i) =>
-        lines[i].startsWith(_reflinkDefinitionStart);
-
-    var i = 0;
-    loopOverDefinitions:
-    while (true) {
-      // Check for reflink definitions.
-      if (!lineStartsReflinkDefinition(i)) {
-        // It's paragraph content from here on out.
-        break;
-      }
-      var contents = lines[i];
-      var j = i + 1;
-      while (j < lines.length) {
-        // Check to see if the _next_ line might start a reflink definition.
-        // Even if it turns out not to be, but it started with a '[', then it
-        // is not a part of _this_ possible reflink definition.
-        if (lineStartsReflinkDefinition(j)) {
-          // Try to parse [contents] as a reflink definition.
-          if (_parseReflinkDefinition(parser, contents)) {
-            // Loop again, starting at the next possible reflink definition.
-            i = j;
-            continue loopOverDefinitions;
-          } else {
-            // Could not parse [contents] as a reflink definition.
-            break;
-          }
-        } else {
-          contents = '$contents\n${lines[j]}';
-          j++;
-        }
-      }
-      // End of the block.
-      if (_parseReflinkDefinition(parser, contents)) {
-        i = j;
-        break;
-      }
-
-      // It may be that there is a reflink definition starting at [i], but it
-      // does not extend all the way to [j], such as:
-      //
-      //     [link]: url     // line i
-      //     "title"
-      //     garbage
-      //     [link2]: url   // line j
-      //
-      // In this case, [i, i+1] is a reflink definition, and the rest is
-      // paragraph content.
-      while (j >= i) {
-        // This isn't the most efficient loop, what with this big ole'
-        // Iterable allocation (`getRange`) followed by a big 'ole String
-        // allocation, but we
-        // must walk backwards, checking each range.
-        contents = lines.getRange(i, j).join('\n');
-        if (_parseReflinkDefinition(parser, contents)) {
-          // That is the last reflink definition. The rest is paragraph
-          // content.
-          i = j;
-          break;
-        }
-        j--;
-      }
-      // The ending was not a reflink definition at all. Just paragraph
-      // content.
-
-      break;
-    }
-
-    if (i == lines.length) {
-      // No paragraph content.
-      return null;
-    } else {
-      // Ends with paragraph content.
-      return lines.sublist(i);
-    }
-  }
-
-  // Parse [contents] as a reference link definition.
-  //
-  // Also adds the reference link definition to the document.
-  //
-  // Returns whether [contents] could be parsed as a reference link definition.
-  bool _parseReflinkDefinition(BlockParser parser, String contents) {
-    final pattern = RegExp(
-        // Leading indentation.
-        r'''^[ ]{0,3}'''
-        // Reference id in brackets, and URL.
-        r'''\[((?:\\\]|[^\]])+)\]:\s*(?:<(\S+)>|(\S+))\s*'''
-        // Title in double or single quotes, or parens.
-        r'''("[^"]+"|'[^']+'|\([^)]+\)|)\s*$''',
-        multiLine: true);
-    final match = pattern.firstMatch(contents);
-    if (match == null) {
-      // Not a reference link definition.
-      return false;
-    }
-    if (match[0]!.length < contents.length) {
-      // Trailing text. No good.
-      return false;
-    }
-
-    var label = match[1]!;
-    final destination = match[2] ?? match[3];
-    var title = match[4];
-
-    // The label must contain at least one non-whitespace character.
-    if (_whitespacePattern.hasMatch(label)) {
-      return false;
-    }
-
-    if (title == '') {
-      // No title.
-      title = null;
-    } else {
-      // Remove "", '', or ().
-      title = title!.substring(1, title.length - 1);
-    }
-
-    // References are case-insensitive, and internal whitespace is compressed.
-    label =
-        label.toLowerCase().trim().replaceAll(_oneOrMoreWhitespacePattern, ' ');
-
-    parser.document.linkReferences
-        .putIfAbsent(label, () => LinkReference(label, destination!, title!));
-    return true;
-  }
-}

+ 0 - 255
frontend/app_flowy/lib/workspace/application/markdown/src/delta_markdown_decoder.dart

@@ -1,255 +0,0 @@
-import 'dart:collection';
-import 'dart:convert';
-
-import 'package:flutter_quill/models/documents/attribute.dart';
-import 'package:flutter_quill/models/quill_delta.dart';
-
-import 'ast.dart' as ast;
-import 'document.dart';
-
-class DeltaMarkdownDecoder extends Converter<String, String> {
-  @override
-  String convert(String input) {
-    final lines = input.replaceAll('\r\n', '\n').split('\n');
-
-    final markdownDocument = Document().parseLines(lines);
-
-    return jsonEncode(_DeltaVisitor().convert(markdownDocument).toJson());
-  }
-}
-
-class _DeltaVisitor implements ast.NodeVisitor {
-  static final _blockTags =
-      RegExp('h1|h2|h3|h4|h5|h6|hr|pre|ul|ol|blockquote|p|pre');
-
-  static final _embedTags = RegExp('hr|img');
-
-  late Delta delta;
-
-  late Queue<Attribute> activeInlineAttributes;
-  Attribute? activeBlockAttribute;
-  late Set<String> uniqueIds;
-
-  ast.Element? previousElement;
-  late ast.Element previousToplevelElement;
-
-  Delta convert(List<ast.Node> nodes) {
-    delta = Delta();
-    activeInlineAttributes = Queue<Attribute>();
-    uniqueIds = <String>{};
-
-    for (final node in nodes) {
-      node.accept(this);
-    }
-
-    // Ensure the delta ends with a newline.
-    if (delta.length > 0 && delta.last.value != '\n') {
-      delta.insert('\n', activeBlockAttribute?.toJson());
-    }
-
-    return delta;
-  }
-
-  @override
-  void visitText(ast.Text text) {
-    // Remove trailing newline
-    //final lines = text.text.trim().split('\n');
-
-    /*
-    final attributes = Map<String, dynamic>();
-    for (final attr in activeInlineAttributes) {
-      attributes.addAll(attr.toJson());
-    }
-
-    for (final l in lines) {
-      delta.insert(l, attributes);
-      delta.insert('\n', activeBlockAttribute.toJson());
-    }*/
-
-    final str = text.text;
-    //if (str.endsWith('\n')) str = str.substring(0, str.length - 1);
-
-    final attributes = <String, dynamic>{};
-    for (final attr in activeInlineAttributes) {
-      attributes.addAll(attr.toJson());
-    }
-
-    var newlineIndex = str.indexOf('\n');
-    var startIndex = 0;
-    while (newlineIndex != -1) {
-      final previousText = str.substring(startIndex, newlineIndex);
-      if (previousText.isNotEmpty) {
-        delta.insert(previousText, attributes.isNotEmpty ? attributes : null);
-      }
-      delta.insert('\n', activeBlockAttribute?.toJson());
-
-      startIndex = newlineIndex + 1;
-      newlineIndex = str.indexOf('\n', newlineIndex + 1);
-    }
-
-    if (startIndex < str.length) {
-      final lastStr = str.substring(startIndex);
-      delta.insert(lastStr, attributes.isNotEmpty ? attributes : null);
-    }
-  }
-
-  @override
-  bool visitElementBefore(ast.Element element) {
-    // Hackish. Separate block-level elements with newlines.
-    final attr = _tagToAttribute(element);
-
-    if (delta.isNotEmpty && _blockTags.firstMatch(element.tag) != null) {
-      if (element.isToplevel) {
-        // If the last active block attribute is not a list, we need to finish
-        // it off.
-        if (previousToplevelElement.tag != 'ul' &&
-            previousToplevelElement.tag != 'ol' &&
-            previousToplevelElement.tag != 'pre' &&
-            previousToplevelElement.tag != 'hr') {
-          delta.insert('\n', activeBlockAttribute?.toJson());
-        }
-
-        // Only separate the blocks if both are paragraphs.
-        //
-        // TODO(kolja): Determine which behavior we really want here.
-        // We can either insert an additional newline or just have the
-        // paragraphs as single lines. Zefyr will by default render two lines
-        // are different paragraphs so for now we will not add an additional
-        // newline here.
-        //
-        // if (previousToplevelElement != null &&
-        //     previousToplevelElement.tag == 'p' &&
-        //     element.tag == 'p') {
-        //   delta.insert('\n');
-        // }
-      } else if (element.tag == 'p' &&
-          previousElement != null &&
-          !previousElement!.isToplevel &&
-          !previousElement!.children!.contains(element)) {
-        // Here we have two children of the same toplevel element. These need
-        // to be separated by additional newlines.
-
-        delta
-          // Finish off the last lower-level block.
-          ..insert('\n', activeBlockAttribute?.toJson())
-          // Add an empty line between the lower-level blocks.
-          ..insert('\n', activeBlockAttribute?.toJson());
-      }
-    }
-
-    // Keep track of the top-level block attribute.
-    if (element.isToplevel && element.tag != 'hr') {
-      // Hacky solution for horizontal rule so that the attribute is not added
-      // to the line feed at the end of the line.
-      activeBlockAttribute = attr;
-    }
-
-    if (_embedTags.firstMatch(element.tag) != null) {
-      // We write out the element here since the embed has no children or
-      // content.
-      delta.insert(attr!.toJson());
-    } else if (_blockTags.firstMatch(element.tag) == null && attr != null) {
-      activeInlineAttributes.addLast(attr);
-    }
-
-    previousElement = element;
-    if (element.isToplevel) {
-      previousToplevelElement = element;
-    }
-
-    if (element.isEmpty) {
-      // Empty element like <hr/>.
-      //buffer.write(' />');
-
-      if (element.tag == 'br') {
-        delta.insert('\n');
-      }
-
-      return false;
-    } else {
-      //buffer.write('>');
-      return true;
-    }
-  }
-
-  @override
-  void visitElementAfter(ast.Element element) {
-    if (element.tag == 'li' &&
-        (previousToplevelElement.tag == 'ol' ||
-            previousToplevelElement.tag == 'ul')) {
-      delta.insert('\n', activeBlockAttribute?.toJson());
-    }
-
-    final attr = _tagToAttribute(element);
-    if (attr == null || !attr.isInline || activeInlineAttributes.last != attr) {
-      return;
-    }
-    activeInlineAttributes.removeLast();
-
-    // Always keep track of the last element.
-    // This becomes relevant if we have something like
-    //
-    // <ul>
-    //   <li>...</li>
-    //   <li>...</li>
-    // </ul>
-    previousElement = element;
-  }
-
-  /// Uniquifies an id generated from text.
-  String uniquifyId(String id) {
-    if (!uniqueIds.contains(id)) {
-      uniqueIds.add(id);
-      return id;
-    }
-
-    var suffix = 2;
-    var suffixedId = '$id-$suffix';
-    while (uniqueIds.contains(suffixedId)) {
-      suffixedId = '$id-${suffix++}';
-    }
-    uniqueIds.add(suffixedId);
-    return suffixedId;
-  }
-
-  Attribute? _tagToAttribute(ast.Element el) {
-    switch (el.tag) {
-      case 'em':
-        return Attribute.italic;
-      case 'strong':
-        return Attribute.bold;
-      case 'ul':
-        return Attribute.ul;
-      case 'ol':
-        return Attribute.ol;
-      case 'pre':
-        return Attribute.codeBlock;
-      case 'blockquote':
-        return Attribute.blockQuote;
-      case 'h1':
-        return Attribute.h1;
-      case 'h2':
-        return Attribute.h2;
-      case 'h3':
-        return Attribute.h3;
-      case 'a':
-        final href = el.attributes['href'];
-        return LinkAttribute(href);
-      case 'img':
-        final href = el.attributes['src'];
-        return ImageAttribute(href);
-      case 'hr':
-        return DividerAttribute();
-    }
-
-    return null;
-  }
-}
-
-class ImageAttribute extends Attribute<String?> {
-  ImageAttribute(String? val) : super('image', AttributeScope.EMBEDS, val);
-}
-
-class DividerAttribute extends Attribute<String?> {
-  DividerAttribute() : super('divider', AttributeScope.EMBEDS, 'hr');
-}

+ 0 - 284
frontend/app_flowy/lib/workspace/application/markdown/src/delta_markdown_encoder.dart

@@ -1,284 +0,0 @@
-import 'dart:convert';
-
-import 'package:collection/collection.dart' show IterableExtension;
-import 'package:flutter_quill/models/documents/attribute.dart';
-import 'package:flutter_quill/models/documents/nodes/embed.dart';
-import 'package:flutter_quill/models/documents/style.dart';
-import 'package:flutter_quill/models/quill_delta.dart';
-
-class DeltaMarkdownEncoder extends Converter<String, String> {
-  static const _lineFeedAsciiCode = 0x0A;
-
-  late StringBuffer markdownBuffer;
-  late StringBuffer lineBuffer;
-
-  Attribute? currentBlockStyle;
-  late Style currentInlineStyle;
-
-  late List<String> currentBlockLines;
-
-  /// Converts the [input] delta to Markdown.
-  @override
-  String convert(String input) {
-    markdownBuffer = StringBuffer();
-    lineBuffer = StringBuffer();
-    currentInlineStyle = Style();
-    currentBlockLines = <String>[];
-
-    final inputJson = jsonDecode(input) as List<dynamic>?;
-    if (inputJson is! List<dynamic>) {
-      throw ArgumentError('Unexpected formatting of the input delta string.');
-    }
-    final delta = Delta.fromJson(inputJson);
-    final iterator = DeltaIterator(delta);
-
-    while (iterator.hasNext) {
-      final operation = iterator.next();
-
-      if (operation.data is String) {
-        final operationData = operation.data as String;
-
-        if (!operationData.contains('\n')) {
-          _handleInline(lineBuffer, operationData, operation.attributes);
-        } else {
-          _handleLine(operationData, operation.attributes);
-        }
-      } else if (operation.data is Map<String, dynamic>) {
-        _handleEmbed(operation.data as Map<String, dynamic>);
-      } else {
-        throw ArgumentError('Unexpected formatting of the input delta string.');
-      }
-    }
-
-    _handleBlock(currentBlockStyle); // Close the last block
-
-    return markdownBuffer.toString();
-  }
-
-  void _handleInline(
-    StringBuffer buffer,
-    String text,
-    Map<String, dynamic>? attributes,
-  ) {
-    final style = Style.fromJson(attributes);
-
-    // First close any current styles if needed
-    final markedForRemoval = <Attribute>[];
-    // Close the styles in reverse order, e.g. **_ for _**Test**_.
-    for (final value
-        in currentInlineStyle.attributes.values.toList().reversed) {
-      // TODO(tillf): Is block correct?
-      if (value.scope == AttributeScope.BLOCK) {
-        continue;
-      }
-      if (style.containsKey(value.key)) {
-        continue;
-      }
-
-      final padding = _trimRight(buffer);
-      _writeAttribute(buffer, value, close: true);
-      if (padding.isNotEmpty) {
-        buffer.write(padding);
-      }
-      markedForRemoval.add(value);
-    }
-
-    // Make sure to remove all attributes that are marked for removal.
-    for (final value in markedForRemoval) {
-      currentInlineStyle.attributes.removeWhere((_, v) => v == value);
-    }
-
-    // Now open any new styles.
-    for (final attribute in style.attributes.values) {
-      // TODO(tillf): Is block correct?
-      if (attribute.scope == AttributeScope.BLOCK) {
-        continue;
-      }
-      if (currentInlineStyle.containsKey(attribute.key)) {
-        continue;
-      }
-      final originalText = text;
-      text = text.trimLeft();
-      final padding = ' ' * (originalText.length - text.length);
-      if (padding.isNotEmpty) {
-        buffer.write(padding);
-      }
-      _writeAttribute(buffer, attribute);
-    }
-
-    // Write the text itself
-    buffer.write(text);
-    currentInlineStyle = style;
-  }
-
-  void _handleLine(String data, Map<String, dynamic>? attributes) {
-    final span = StringBuffer();
-
-    for (var i = 0; i < data.length; i++) {
-      if (data.codeUnitAt(i) == _lineFeedAsciiCode) {
-        if (span.isNotEmpty) {
-          // Write the span if it's not empty.
-          _handleInline(lineBuffer, span.toString(), attributes);
-        }
-        // Close any open inline styles.
-        _handleInline(lineBuffer, '', null);
-
-        final lineBlock = Style.fromJson(attributes)
-            .attributes
-            .values
-            .singleWhereOrNull((a) => a.scope == AttributeScope.BLOCK);
-
-        if (lineBlock == currentBlockStyle) {
-          currentBlockLines.add(lineBuffer.toString());
-        } else {
-          _handleBlock(currentBlockStyle);
-          currentBlockLines
-            ..clear()
-            ..add(lineBuffer.toString());
-
-          currentBlockStyle = lineBlock;
-        }
-        lineBuffer.clear();
-
-        span.clear();
-      } else {
-        span.writeCharCode(data.codeUnitAt(i));
-      }
-    }
-
-    // Remaining span
-    if (span.isNotEmpty) {
-      _handleInline(lineBuffer, span.toString(), attributes);
-    }
-  }
-
-  void _handleEmbed(Map<String, dynamic> data) {
-    final embed = BlockEmbed(data.keys.first, data.values.first as String);
-
-    if (embed.type == 'image') {
-      _writeEmbedTag(lineBuffer, embed);
-      _writeEmbedTag(lineBuffer, embed, close: true);
-    } else if (embed.type == 'divider') {
-      _writeEmbedTag(lineBuffer, embed);
-      _writeEmbedTag(lineBuffer, embed, close: true);
-    }
-  }
-
-  void _handleBlock(Attribute? blockStyle) {
-    if (currentBlockLines.isEmpty) {
-      return; // Empty block
-    }
-
-    // If there was a block before this one, add empty line between the blocks
-    if (markdownBuffer.isNotEmpty) {
-      markdownBuffer.writeln();
-    }
-
-    if (blockStyle == null) {
-      markdownBuffer
-        ..write(currentBlockLines.join('\n'))
-        ..writeln();
-    } else if (blockStyle == Attribute.codeBlock) {
-      _writeAttribute(markdownBuffer, blockStyle);
-      markdownBuffer.write(currentBlockLines.join('\n'));
-      _writeAttribute(markdownBuffer, blockStyle, close: true);
-      markdownBuffer.writeln();
-    } else {
-      // Dealing with lists or a quote.
-      for (final line in currentBlockLines) {
-        _writeBlockTag(markdownBuffer, blockStyle);
-        markdownBuffer
-          ..write(line)
-          ..writeln();
-      }
-    }
-  }
-
-  String _trimRight(StringBuffer buffer) {
-    final text = buffer.toString();
-    if (!text.endsWith(' ')) {
-      return '';
-    }
-
-    final result = text.trimRight();
-    buffer
-      ..clear()
-      ..write(result);
-    return ' ' * (text.length - result.length);
-  }
-
-  void _writeAttribute(
-    StringBuffer buffer,
-    Attribute attribute, {
-    bool close = false,
-  }) {
-    if (attribute.key == Attribute.bold.key) {
-      buffer.write('**');
-    } else if (attribute.key == Attribute.italic.key) {
-      buffer.write('_');
-    } else if (attribute.key == Attribute.link.key) {
-      buffer.write(!close ? '[' : '](${attribute.value})');
-    } else if (attribute == Attribute.codeBlock) {
-      buffer.write(!close ? '```\n' : '\n```');
-    } else if (attribute.key == Attribute.background.key) {
-      buffer.write(!close ? '<mark>' : '</mark>');
-    } else if (attribute.key == Attribute.underline.key) {
-      buffer.write(!close ? '<u>' : '</u>');
-    } else if (attribute.key == Attribute.codeBlock.key) {
-      buffer.write(!close ? '```\n' : '\n```');
-    } else if (attribute.key == Attribute.inlineCode.key) {
-      buffer.write(!close ? '`' : '`');
-    } else if (attribute.key == Attribute.strikeThrough.key) {
-      buffer.write(!close ? '~~' : '~~');
-    } else {
-      // do nothing, just skip the unknown attribute.
-    }
-  }
-
-  void _writeBlockTag(
-    StringBuffer buffer,
-    Attribute block, {
-    bool close = false,
-  }) {
-    if (close) {
-      return; // no close tag needed for simple blocks.
-    }
-
-    if (block == Attribute.blockQuote) {
-      buffer.write('> ');
-    } else if (block == Attribute.ul) {
-      buffer.write('* ');
-    } else if (block == Attribute.ol) {
-      buffer.write('1. ');
-    } else if (block.key == Attribute.h1.key && block.value == 1) {
-      buffer.write('# ');
-    } else if (block.key == Attribute.h2.key && block.value == 2) {
-      buffer.write('## ');
-    } else if (block.key == Attribute.h3.key && block.value == 3) {
-      buffer.write('### ');
-    } else if (block.key == Attribute.list.key) {
-      buffer.write('* ');
-    } else {
-      // do nothing, just skip the unknown attribute.
-    }
-  }
-
-  void _writeEmbedTag(
-    StringBuffer buffer,
-    BlockEmbed embed, {
-    bool close = false,
-  }) {
-    const kImageType = 'image';
-    const kDividerType = 'divider';
-
-    if (embed.type == kImageType) {
-      if (close) {
-        buffer.write('](${embed.data})');
-      } else {
-        buffer.write('![');
-      }
-    } else if (embed.type == kDividerType && close) {
-      buffer.write('\n---\n\n');
-    }
-  }
-}

+ 0 - 88
frontend/app_flowy/lib/workspace/application/markdown/src/document.dart

@@ -1,88 +0,0 @@
-// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'ast.dart';
-import 'block_parser.dart';
-import 'extension_set.dart';
-import 'inline_parser.dart';
-
-/// Maintains the context needed to parse a Markdown document.
-class Document {
-  Document({
-    Iterable<BlockSyntax>? blockSyntaxes,
-    Iterable<InlineSyntax>? inlineSyntaxes,
-    ExtensionSet? extensionSet,
-    this.linkResolver,
-    this.imageLinkResolver,
-  }) : extensionSet = extensionSet ?? ExtensionSet.commonMark {
-    _blockSyntaxes
-      ..addAll(blockSyntaxes ?? [])
-      ..addAll(this.extensionSet.blockSyntaxes);
-    _inlineSyntaxes
-      ..addAll(inlineSyntaxes ?? [])
-      ..addAll(this.extensionSet.inlineSyntaxes);
-  }
-
-  final Map<String, LinkReference> linkReferences = <String, LinkReference>{};
-  final ExtensionSet extensionSet;
-  final Resolver? linkResolver;
-  final Resolver? imageLinkResolver;
-  final _blockSyntaxes = <BlockSyntax>{};
-  final _inlineSyntaxes = <InlineSyntax>{};
-
-  Iterable<BlockSyntax> get blockSyntaxes => _blockSyntaxes;
-  Iterable<InlineSyntax> get inlineSyntaxes => _inlineSyntaxes;
-
-  /// Parses the given [lines] of Markdown to a series of AST nodes.
-  List<Node> parseLines(List<String> lines) {
-    final nodes = BlockParser(lines, this).parseLines();
-    // Make sure to mark the top level nodes as such.
-    for (final n in nodes) {
-      n.isToplevel = true;
-    }
-    _parseInlineContent(nodes);
-    return nodes;
-  }
-
-  /// Parses the given inline Markdown [text] to a series of AST nodes.
-  List<Node>? parseInline(String text) => InlineParser(text, this).parse();
-
-  void _parseInlineContent(List<Node> nodes) {
-    for (var i = 0; i < nodes.length; i++) {
-      final node = nodes[i];
-      if (node is UnparsedContent) {
-        final inlineNodes = parseInline(node.textContent)!;
-        nodes
-          ..removeAt(i)
-          ..insertAll(i, inlineNodes);
-        i += inlineNodes.length - 1;
-      } else if (node is Element && node.children != null) {
-        _parseInlineContent(node.children!);
-      }
-    }
-  }
-}
-
-/// A [link reference
-/// definition](http://spec.commonmark.org/0.28/#link-reference-definitions).
-class LinkReference {
-  /// Construct a [LinkReference], with all necessary fields.
-  ///
-  /// If the parsed link reference definition does not include a title, use
-  /// `null` for the [title] parameter.
-  LinkReference(this.label, this.destination, this.title);
-
-  /// The [link label](http://spec.commonmark.org/0.28/#link-label).
-  ///
-  /// Temporarily, this class is also being used to represent the link data for
-  /// an inline link (the destination and title), but this should change before
-  /// the package is released.
-  final String label;
-
-  /// The [link destination](http://spec.commonmark.org/0.28/#link-destination).
-  final String destination;
-
-  /// The [link title](http://spec.commonmark.org/0.28/#link-title).
-  final String title;
-}

+ 0 - 1818
frontend/app_flowy/lib/workspace/application/markdown/src/emojis.dart

@@ -1,1818 +0,0 @@
-// This file was generated by 0xN0x using emojilib's emoji data file:
-// https://github.com/muan/unicode-emoji-json/raw/main/data-by-emoji.json
-// at 2021-12-01 10:10:53
-// Emoji version 13.1
-
-const emojis = <String, String>{
-  'grinning_face': '😀',
-  'grinning_face_with_big_eyes': '😃',
-  'grinning_face_with_smiling_eyes': '😄',
-  'beaming_face_with_smiling_eyes': '😁',
-  'grinning_squinting_face': '😆',
-  'grinning_face_with_sweat': '😅',
-  'rolling_on_the_floor_laughing': '🤣',
-  'face_with_tears_of_joy': '😂',
-  'slightly_smiling_face': '🙂',
-  'upside_down_face': '🙃',
-  'winking_face': '😉',
-  'smiling_face_with_smiling_eyes': '😊',
-  'smiling_face_with_halo': '😇',
-  'smiling_face_with_hearts': '🥰',
-  'smiling_face_with_heart_eyes': '😍',
-  'star_struck': '🤩',
-  'face_blowing_a_kiss': '😘',
-  'kissing_face': '😗',
-  'smiling_face': '☺️',
-  'kissing_face_with_closed_eyes': '😚',
-  'kissing_face_with_smiling_eyes': '😙',
-  'smiling_face_with_tear': '🥲',
-  'face_savoring_food': '😋',
-  'face_with_tongue': '😛',
-  'winking_face_with_tongue': '😜',
-  'zany_face': '🤪',
-  'squinting_face_with_tongue': '😝',
-  'money_mouth_face': '🤑',
-  'hugging_face': '🤗',
-  'face_with_hand_over_mouth': '🤭',
-  'shushing_face': '🤫',
-  'thinking_face': '🤔',
-  'zipper_mouth_face': '🤐',
-  'face_with_raised_eyebrow': '🤨',
-  'neutral_face': '😐',
-  'expressionless_face': '😑',
-  'face_without_mouth': '😶',
-  'face_in_clouds': '😶‍🌫️',
-  'smirking_face': '😏',
-  'unamused_face': '😒',
-  'face_with_rolling_eyes': '🙄',
-  'grimacing_face': '😬',
-  'face_exhaling': '😮‍💨',
-  'lying_face': '🤥',
-  'relieved_face': '😌',
-  'pensive_face': '😔',
-  'sleepy_face': '😪',
-  'drooling_face': '🤤',
-  'sleeping_face': '😴',
-  'face_with_medical_mask': '😷',
-  'face_with_thermometer': '🤒',
-  'face_with_head_bandage': '🤕',
-  'nauseated_face': '🤢',
-  'face_vomiting': '🤮',
-  'sneezing_face': '🤧',
-  'hot_face': '🥵',
-  'cold_face': '🥶',
-  'woozy_face': '🥴',
-  'knocked_out_face': '😵',
-  'face_with_spiral_eyes': '😵‍💫',
-  'exploding_head': '🤯',
-  'cowboy_hat_face': '🤠',
-  'partying_face': '🥳',
-  'disguised_face': '🥸',
-  'smiling_face_with_sunglasses': '😎',
-  'nerd_face': '🤓',
-  'face_with_monocle': '🧐',
-  'confused_face': '😕',
-  'worried_face': '😟',
-  'slightly_frowning_face': '🙁',
-  'frowning_face': '☹️',
-  'face_with_open_mouth': '😮',
-  'hushed_face': '😯',
-  'astonished_face': '😲',
-  'flushed_face': '😳',
-  'pleading_face': '🥺',
-  'frowning_face_with_open_mouth': '😦',
-  'anguished_face': '😧',
-  'fearful_face': '😨',
-  'anxious_face_with_sweat': '😰',
-  'sad_but_relieved_face': '😥',
-  'crying_face': '😢',
-  'loudly_crying_face': '😭',
-  'face_screaming_in_fear': '😱',
-  'confounded_face': '😖',
-  'persevering_face': '😣',
-  'disappointed_face': '😞',
-  'downcast_face_with_sweat': '😓',
-  'weary_face': '😩',
-  'tired_face': '😫',
-  'yawning_face': '🥱',
-  'face_with_steam_from_nose': '😤',
-  'pouting_face': '😡',
-  'angry_face': '😠',
-  'face_with_symbols_on_mouth': '🤬',
-  'smiling_face_with_horns': '😈',
-  'angry_face_with_horns': '👿',
-  'skull': '💀',
-  'skull_and_crossbones': '☠️',
-  'pile_of_poo': '💩',
-  'clown_face': '🤡',
-  'ogre': '👹',
-  'goblin': '👺',
-  'ghost': '👻',
-  'alien': '👽',
-  'alien_monster': '👾',
-  'robot': '🤖',
-  'grinning_cat': '😺',
-  'grinning_cat_with_smiling_eyes': '😸',
-  'cat_with_tears_of_joy': '😹',
-  'smiling_cat_with_heart_eyes': '😻',
-  'cat_with_wry_smile': '😼',
-  'kissing_cat': '😽',
-  'weary_cat': '🙀',
-  'crying_cat': '😿',
-  'pouting_cat': '😾',
-  'see_no_evil_monkey': '🙈',
-  'hear_no_evil_monkey': '🙉',
-  'speak_no_evil_monkey': '🙊',
-  'kiss_mark': '💋',
-  'love_letter': '💌',
-  'heart_with_arrow': '💘',
-  'heart_with_ribbon': '💝',
-  'sparkling_heart': '💖',
-  'growing_heart': '💗',
-  'beating_heart': '💓',
-  'revolving_hearts': '💞',
-  'two_hearts': '💕',
-  'heart_decoration': '💟',
-  'heart_exclamation': '❣️',
-  'broken_heart': '💔',
-  'heart_on_fire': '❤️‍🔥',
-  'mending_heart': '❤️‍🩹',
-  'red_heart': '❤️',
-  'orange_heart': '🧡',
-  'yellow_heart': '💛',
-  'green_heart': '💚',
-  'blue_heart': '💙',
-  'purple_heart': '💜',
-  'brown_heart': '🤎',
-  'black_heart': '🖤',
-  'white_heart': '🤍',
-  'hundred_points': '💯',
-  'anger_symbol': '💢',
-  'collision': '💥',
-  'dizzy': '💫',
-  'sweat_droplets': '💦',
-  'dashing_away': '💨',
-  'hole': '🕳️',
-  'bomb': '💣',
-  'speech_balloon': '💬',
-  'eye_in_speech_bubble': '👁️‍🗨️',
-  'left_speech_bubble': '🗨️',
-  'right_anger_bubble': '🗯️',
-  'thought_balloon': '💭',
-  'zzz': '💤',
-  'waving_hand': '👋',
-  'raised_back_of_hand': '🤚',
-  'hand_with_fingers_splayed': '🖐️',
-  'raised_hand': '✋',
-  'vulcan_salute': '🖖',
-  'ok_hand': '👌',
-  'pinched_fingers': '🤌',
-  'pinching_hand': '🤏',
-  'victory_hand': '✌️',
-  'crossed_fingers': '🤞',
-  'love_you_gesture': '🤟',
-  'sign_of_the_horns': '🤘',
-  'call_me_hand': '🤙',
-  'backhand_index_pointing_left': '👈',
-  'backhand_index_pointing_right': '👉',
-  'backhand_index_pointing_up': '👆',
-  'middle_finger': '🖕',
-  'backhand_index_pointing_down': '👇',
-  'index_pointing_up': '☝️',
-  'thumbs_up': '👍',
-  'thumbs_down': '👎',
-  'raised_fist': '✊',
-  'oncoming_fist': '👊',
-  'left_facing_fist': '🤛',
-  'right_facing_fist': '🤜',
-  'clapping_hands': '👏',
-  'raising_hands': '🙌',
-  'open_hands': '👐',
-  'palms_up_together': '🤲',
-  'handshake': '🤝',
-  'folded_hands': '🙏',
-  'writing_hand': '✍️',
-  'nail_polish': '💅',
-  'selfie': '🤳',
-  'flexed_biceps': '💪',
-  'mechanical_arm': '🦾',
-  'mechanical_leg': '🦿',
-  'leg': '🦵',
-  'foot': '🦶',
-  'ear': '👂',
-  'ear_with_hearing_aid': '🦻',
-  'nose': '👃',
-  'brain': '🧠',
-  'anatomical_heart': '🫀',
-  'lungs': '🫁',
-  'tooth': '🦷',
-  'bone': '🦴',
-  'eyes': '👀',
-  'eye': '👁️',
-  'tongue': '👅',
-  'mouth': '👄',
-  'baby': '👶',
-  'child': '🧒',
-  'boy': '👦',
-  'girl': '👧',
-  'person': '🧑',
-  'person_blond_hair': '👱',
-  'man': '👨',
-  'person_beard': '🧔',
-  'man_beard': '🧔‍♂️',
-  'woman_beard': '🧔‍♀️',
-  'man_red_hair': '👨‍🦰',
-  'man_curly_hair': '👨‍🦱',
-  'man_white_hair': '👨‍🦳',
-  'man_bald': '👨‍🦲',
-  'woman': '👩',
-  'woman_red_hair': '👩‍🦰',
-  'person_red_hair': '🧑‍🦰',
-  'woman_curly_hair': '👩‍🦱',
-  'person_curly_hair': '🧑‍🦱',
-  'woman_white_hair': '👩‍🦳',
-  'person_white_hair': '🧑‍🦳',
-  'woman_bald': '👩‍🦲',
-  'person_bald': '🧑‍🦲',
-  'woman_blond_hair': '👱‍♀️',
-  'man_blond_hair': '👱‍♂️',
-  'older_person': '🧓',
-  'old_man': '👴',
-  'old_woman': '👵',
-  'person_frowning': '🙍',
-  'man_frowning': '🙍‍♂️',
-  'woman_frowning': '🙍‍♀️',
-  'person_pouting': '🙎',
-  'man_pouting': '🙎‍♂️',
-  'woman_pouting': '🙎‍♀️',
-  'person_gesturing_no': '🙅',
-  'man_gesturing_no': '🙅‍♂️',
-  'woman_gesturing_no': '🙅‍♀️',
-  'person_gesturing_ok': '🙆',
-  'man_gesturing_ok': '🙆‍♂️',
-  'woman_gesturing_ok': '🙆‍♀️',
-  'person_tipping_hand': '💁',
-  'man_tipping_hand': '💁‍♂️',
-  'woman_tipping_hand': '💁‍♀️',
-  'person_raising_hand': '🙋',
-  'man_raising_hand': '🙋‍♂️',
-  'woman_raising_hand': '🙋‍♀️',
-  'deaf_person': '🧏',
-  'deaf_man': '🧏‍♂️',
-  'deaf_woman': '🧏‍♀️',
-  'person_bowing': '🙇',
-  'man_bowing': '🙇‍♂️',
-  'woman_bowing': '🙇‍♀️',
-  'person_facepalming': '🤦',
-  'man_facepalming': '🤦‍♂️',
-  'woman_facepalming': '🤦‍♀️',
-  'person_shrugging': '🤷',
-  'man_shrugging': '🤷‍♂️',
-  'woman_shrugging': '🤷‍♀️',
-  'health_worker': '🧑‍⚕️',
-  'man_health_worker': '👨‍⚕️',
-  'woman_health_worker': '👩‍⚕️',
-  'student': '🧑‍🎓',
-  'man_student': '👨‍🎓',
-  'woman_student': '👩‍🎓',
-  'teacher': '🧑‍🏫',
-  'man_teacher': '👨‍🏫',
-  'woman_teacher': '👩‍🏫',
-  'judge': '🧑‍⚖️',
-  'man_judge': '👨‍⚖️',
-  'woman_judge': '👩‍⚖️',
-  'farmer': '🧑‍🌾',
-  'man_farmer': '👨‍🌾',
-  'woman_farmer': '👩‍🌾',
-  'cook': '🧑‍🍳',
-  'man_cook': '👨‍🍳',
-  'woman_cook': '👩‍🍳',
-  'mechanic': '🧑‍🔧',
-  'man_mechanic': '👨‍🔧',
-  'woman_mechanic': '👩‍🔧',
-  'factory_worker': '🧑‍🏭',
-  'man_factory_worker': '👨‍🏭',
-  'woman_factory_worker': '👩‍🏭',
-  'office_worker': '🧑‍💼',
-  'man_office_worker': '👨‍💼',
-  'woman_office_worker': '👩‍💼',
-  'scientist': '🧑‍🔬',
-  'man_scientist': '👨‍🔬',
-  'woman_scientist': '👩‍🔬',
-  'technologist': '🧑‍💻',
-  'man_technologist': '👨‍💻',
-  'woman_technologist': '👩‍💻',
-  'singer': '🧑‍🎤',
-  'man_singer': '👨‍🎤',
-  'woman_singer': '👩‍🎤',
-  'artist': '🧑‍🎨',
-  'man_artist': '👨‍🎨',
-  'woman_artist': '👩‍🎨',
-  'pilot': '🧑‍✈️',
-  'man_pilot': '👨‍✈️',
-  'woman_pilot': '👩‍✈️',
-  'astronaut': '🧑‍🚀',
-  'man_astronaut': '👨‍🚀',
-  'woman_astronaut': '👩‍🚀',
-  'firefighter': '🧑‍🚒',
-  'man_firefighter': '👨‍🚒',
-  'woman_firefighter': '👩‍🚒',
-  'police_officer': '👮',
-  'man_police_officer': '👮‍♂️',
-  'woman_police_officer': '👮‍♀️',
-  'detective': '🕵️',
-  'man_detective': '🕵️‍♂️',
-  'woman_detective': '🕵️‍♀️',
-  'guard': '💂',
-  'man_guard': '💂‍♂️',
-  'woman_guard': '💂‍♀️',
-  'ninja': '🥷',
-  'construction_worker': '👷',
-  'man_construction_worker': '👷‍♂️',
-  'woman_construction_worker': '👷‍♀️',
-  'prince': '🤴',
-  'princess': '👸',
-  'person_wearing_turban': '👳',
-  'man_wearing_turban': '👳‍♂️',
-  'woman_wearing_turban': '👳‍♀️',
-  'person_with_skullcap': '👲',
-  'woman_with_headscarf': '🧕',
-  'person_in_tuxedo': '🤵',
-  'man_in_tuxedo': '🤵‍♂️',
-  'woman_in_tuxedo': '🤵‍♀️',
-  'person_with_veil': '👰',
-  'man_with_veil': '👰‍♂️',
-  'woman_with_veil': '👰‍♀️',
-  'pregnant_woman': '🤰',
-  'breast_feeding': '🤱',
-  'woman_feeding_baby': '👩‍🍼',
-  'man_feeding_baby': '👨‍🍼',
-  'person_feeding_baby': '🧑‍🍼',
-  'baby_angel': '👼',
-  'santa_claus': '🎅',
-  'mrs_claus': '🤶',
-  'mx_claus': '🧑‍🎄',
-  'superhero': '🦸',
-  'man_superhero': '🦸‍♂️',
-  'woman_superhero': '🦸‍♀️',
-  'supervillain': '🦹',
-  'man_supervillain': '🦹‍♂️',
-  'woman_supervillain': '🦹‍♀️',
-  'mage': '🧙',
-  'man_mage': '🧙‍♂️',
-  'woman_mage': '🧙‍♀️',
-  'fairy': '🧚',
-  'man_fairy': '🧚‍♂️',
-  'woman_fairy': '🧚‍♀️',
-  'vampire': '🧛',
-  'man_vampire': '🧛‍♂️',
-  'woman_vampire': '🧛‍♀️',
-  'merperson': '🧜',
-  'merman': '🧜‍♂️',
-  'mermaid': '🧜‍♀️',
-  'elf': '🧝',
-  'man_elf': '🧝‍♂️',
-  'woman_elf': '🧝‍♀️',
-  'genie': '🧞',
-  'man_genie': '🧞‍♂️',
-  'woman_genie': '🧞‍♀️',
-  'zombie': '🧟',
-  'man_zombie': '🧟‍♂️',
-  'woman_zombie': '🧟‍♀️',
-  'person_getting_massage': '💆',
-  'man_getting_massage': '💆‍♂️',
-  'woman_getting_massage': '💆‍♀️',
-  'person_getting_haircut': '💇',
-  'man_getting_haircut': '💇‍♂️',
-  'woman_getting_haircut': '💇‍♀️',
-  'person_walking': '🚶',
-  'man_walking': '🚶‍♂️',
-  'woman_walking': '🚶‍♀️',
-  'person_standing': '🧍',
-  'man_standing': '🧍‍♂️',
-  'woman_standing': '🧍‍♀️',
-  'person_kneeling': '🧎',
-  'man_kneeling': '🧎‍♂️',
-  'woman_kneeling': '🧎‍♀️',
-  'person_with_white_cane': '🧑‍🦯',
-  'man_with_white_cane': '👨‍🦯',
-  'woman_with_white_cane': '👩‍🦯',
-  'person_in_motorized_wheelchair': '🧑‍🦼',
-  'man_in_motorized_wheelchair': '👨‍🦼',
-  'woman_in_motorized_wheelchair': '👩‍🦼',
-  'person_in_manual_wheelchair': '🧑‍🦽',
-  'man_in_manual_wheelchair': '👨‍🦽',
-  'woman_in_manual_wheelchair': '👩‍🦽',
-  'person_running': '🏃',
-  'man_running': '🏃‍♂️',
-  'woman_running': '🏃‍♀️',
-  'woman_dancing': '💃',
-  'man_dancing': '🕺',
-  'person_in_suit_levitating': '🕴️',
-  'people_with_bunny_ears': '👯',
-  'men_with_bunny_ears': '👯‍♂️',
-  'women_with_bunny_ears': '👯‍♀️',
-  'person_in_steamy_room': '🧖',
-  'man_in_steamy_room': '🧖‍♂️',
-  'woman_in_steamy_room': '🧖‍♀️',
-  'person_climbing': '🧗',
-  'man_climbing': '🧗‍♂️',
-  'woman_climbing': '🧗‍♀️',
-  'person_fencing': '🤺',
-  'horse_racing': '🏇',
-  'skier': '⛷️',
-  'snowboarder': '🏂',
-  'person_golfing': '🏌️',
-  'man_golfing': '🏌️‍♂️',
-  'woman_golfing': '🏌️‍♀️',
-  'person_surfing': '🏄',
-  'man_surfing': '🏄‍♂️',
-  'woman_surfing': '🏄‍♀️',
-  'person_rowing_boat': '🚣',
-  'man_rowing_boat': '🚣‍♂️',
-  'woman_rowing_boat': '🚣‍♀️',
-  'person_swimming': '🏊',
-  'man_swimming': '🏊‍♂️',
-  'woman_swimming': '🏊‍♀️',
-  'person_bouncing_ball': '⛹️',
-  'man_bouncing_ball': '⛹️‍♂️',
-  'woman_bouncing_ball': '⛹️‍♀️',
-  'person_lifting_weights': '🏋️',
-  'man_lifting_weights': '🏋️‍♂️',
-  'woman_lifting_weights': '🏋️‍♀️',
-  'person_biking': '🚴',
-  'man_biking': '🚴‍♂️',
-  'woman_biking': '🚴‍♀️',
-  'person_mountain_biking': '🚵',
-  'man_mountain_biking': '🚵‍♂️',
-  'woman_mountain_biking': '🚵‍♀️',
-  'person_cartwheeling': '🤸',
-  'man_cartwheeling': '🤸‍♂️',
-  'woman_cartwheeling': '🤸‍♀️',
-  'people_wrestling': '🤼',
-  'men_wrestling': '🤼‍♂️',
-  'women_wrestling': '🤼‍♀️',
-  'person_playing_water_polo': '🤽',
-  'man_playing_water_polo': '🤽‍♂️',
-  'woman_playing_water_polo': '🤽‍♀️',
-  'person_playing_handball': '🤾',
-  'man_playing_handball': '🤾‍♂️',
-  'woman_playing_handball': '🤾‍♀️',
-  'person_juggling': '🤹',
-  'man_juggling': '🤹‍♂️',
-  'woman_juggling': '🤹‍♀️',
-  'person_in_lotus_position': '🧘',
-  'man_in_lotus_position': '🧘‍♂️',
-  'woman_in_lotus_position': '🧘‍♀️',
-  'person_taking_bath': '🛀',
-  'person_in_bed': '🛌',
-  'people_holding_hands': '🧑‍🤝‍🧑',
-  'women_holding_hands': '👭',
-  'woman_and_man_holding_hands': '👫',
-  'men_holding_hands': '👬',
-  'kiss': '💏',
-  'kiss_woman_man': '👩‍❤️‍💋‍👨',
-  'kiss_man_man': '👨‍❤️‍💋‍👨',
-  'kiss_woman_woman': '👩‍❤️‍💋‍👩',
-  'couple_with_heart': '💑',
-  'couple_with_heart_woman_man': '👩‍❤️‍👨',
-  'couple_with_heart_man_man': '👨‍❤️‍👨',
-  'couple_with_heart_woman_woman': '👩‍❤️‍👩',
-  'family': '👪',
-  'family_man_woman_boy': '👨‍👩‍👦',
-  'family_man_woman_girl': '👨‍👩‍👧',
-  'family_man_woman_girl_boy': '👨‍👩‍👧‍👦',
-  'family_man_woman_boy_boy': '👨‍👩‍👦‍👦',
-  'family_man_woman_girl_girl': '👨‍👩‍👧‍👧',
-  'family_man_man_boy': '👨‍👨‍👦',
-  'family_man_man_girl': '👨‍👨‍👧',
-  'family_man_man_girl_boy': '👨‍👨‍👧‍👦',
-  'family_man_man_boy_boy': '👨‍👨‍👦‍👦',
-  'family_man_man_girl_girl': '👨‍👨‍👧‍👧',
-  'family_woman_woman_boy': '👩‍👩‍👦',
-  'family_woman_woman_girl': '👩‍👩‍👧',
-  'family_woman_woman_girl_boy': '👩‍👩‍👧‍👦',
-  'family_woman_woman_boy_boy': '👩‍👩‍👦‍👦',
-  'family_woman_woman_girl_girl': '👩‍👩‍👧‍👧',
-  'family_man_boy': '👨‍👦',
-  'family_man_boy_boy': '👨‍👦‍👦',
-  'family_man_girl': '👨‍👧',
-  'family_man_girl_boy': '👨‍👧‍👦',
-  'family_man_girl_girl': '👨‍👧‍👧',
-  'family_woman_boy': '👩‍👦',
-  'family_woman_boy_boy': '👩‍👦‍👦',
-  'family_woman_girl': '👩‍👧',
-  'family_woman_girl_boy': '👩‍👧‍👦',
-  'family_woman_girl_girl': '👩‍👧‍👧',
-  'speaking_head': '🗣️',
-  'bust_in_silhouette': '👤',
-  'busts_in_silhouette': '👥',
-  'people_hugging': '🫂',
-  'footprints': '👣',
-  'monkey_face': '🐵',
-  'monkey': '🐒',
-  'gorilla': '🦍',
-  'orangutan': '🦧',
-  'dog_face': '🐶',
-  'dog': '🐕',
-  'guide_dog': '🦮',
-  'service_dog': '🐕‍🦺',
-  'poodle': '🐩',
-  'wolf': '🐺',
-  'fox': '🦊',
-  'raccoon': '🦝',
-  'cat_face': '🐱',
-  'cat': '🐈',
-  'black_cat': '🐈‍⬛',
-  'lion': '🦁',
-  'tiger_face': '🐯',
-  'tiger': '🐅',
-  'leopard': '🐆',
-  'horse_face': '🐴',
-  'horse': '🐎',
-  'unicorn': '🦄',
-  'zebra': '🦓',
-  'deer': '🦌',
-  'bison': '🦬',
-  'cow_face': '🐮',
-  'ox': '🐂',
-  'water_buffalo': '🐃',
-  'cow': '🐄',
-  'pig_face': '🐷',
-  'pig': '🐖',
-  'boar': '🐗',
-  'pig_nose': '🐽',
-  'ram': '🐏',
-  'ewe': '🐑',
-  'goat': '🐐',
-  'camel': '🐪',
-  'two_hump_camel': '🐫',
-  'llama': '🦙',
-  'giraffe': '🦒',
-  'elephant': '🐘',
-  'mammoth': '🦣',
-  'rhinoceros': '🦏',
-  'hippopotamus': '🦛',
-  'mouse_face': '🐭',
-  'mouse': '🐁',
-  'rat': '🐀',
-  'hamster': '🐹',
-  'rabbit_face': '🐰',
-  'rabbit': '🐇',
-  'chipmunk': '🐿️',
-  'beaver': '🦫',
-  'hedgehog': '🦔',
-  'bat': '🦇',
-  'bear': '🐻',
-  'polar_bear': '🐻‍❄️',
-  'koala': '🐨',
-  'panda': '🐼',
-  'sloth': '🦥',
-  'otter': '🦦',
-  'skunk': '🦨',
-  'kangaroo': '🦘',
-  'badger': '🦡',
-  'paw_prints': '🐾',
-  'turkey': '🦃',
-  'chicken': '🐔',
-  'rooster': '🐓',
-  'hatching_chick': '🐣',
-  'baby_chick': '🐤',
-  'front_facing_baby_chick': '🐥',
-  'bird': '🐦',
-  'penguin': '🐧',
-  'dove': '🕊️',
-  'eagle': '🦅',
-  'duck': '🦆',
-  'swan': '🦢',
-  'owl': '🦉',
-  'dodo': '🦤',
-  'feather': '🪶',
-  'flamingo': '🦩',
-  'peacock': '🦚',
-  'parrot': '🦜',
-  'frog': '🐸',
-  'crocodile': '🐊',
-  'turtle': '🐢',
-  'lizard': '🦎',
-  'snake': '🐍',
-  'dragon_face': '🐲',
-  'dragon': '🐉',
-  'sauropod': '🦕',
-  't_rex': '🦖',
-  'spouting_whale': '🐳',
-  'whale': '🐋',
-  'dolphin': '🐬',
-  'seal': '🦭',
-  'fish': '🐟',
-  'tropical_fish': '🐠',
-  'blowfish': '🐡',
-  'shark': '🦈',
-  'octopus': '🐙',
-  'spiral_shell': '🐚',
-  'snail': '🐌',
-  'butterfly': '🦋',
-  'bug': '🐛',
-  'ant': '🐜',
-  'honeybee': '🐝',
-  'beetle': '🪲',
-  'lady_beetle': '🐞',
-  'cricket': '🦗',
-  'cockroach': '🪳',
-  'spider': '🕷️',
-  'spider_web': '🕸️',
-  'scorpion': '🦂',
-  'mosquito': '🦟',
-  'fly': '🪰',
-  'worm': '🪱',
-  'microbe': '🦠',
-  'bouquet': '💐',
-  'cherry_blossom': '🌸',
-  'white_flower': '💮',
-  'rosette': '🏵️',
-  'rose': '🌹',
-  'wilted_flower': '🥀',
-  'hibiscus': '🌺',
-  'sunflower': '🌻',
-  'blossom': '🌼',
-  'tulip': '🌷',
-  'seedling': '🌱',
-  'potted_plant': '🪴',
-  'evergreen_tree': '🌲',
-  'deciduous_tree': '🌳',
-  'palm_tree': '🌴',
-  'cactus': '🌵',
-  'sheaf_of_rice': '🌾',
-  'herb': '🌿',
-  'shamrock': '☘️',
-  'four_leaf_clover': '🍀',
-  'maple_leaf': '🍁',
-  'fallen_leaf': '🍂',
-  'leaf_fluttering_in_wind': '🍃',
-  'grapes': '🍇',
-  'melon': '🍈',
-  'watermelon': '🍉',
-  'tangerine': '🍊',
-  'lemon': '🍋',
-  'banana': '🍌',
-  'pineapple': '🍍',
-  'mango': '🥭',
-  'red_apple': '🍎',
-  'green_apple': '🍏',
-  'pear': '🍐',
-  'peach': '🍑',
-  'cherries': '🍒',
-  'strawberry': '🍓',
-  'blueberries': '🫐',
-  'kiwi_fruit': '🥝',
-  'tomato': '🍅',
-  'olive': '🫒',
-  'coconut': '🥥',
-  'avocado': '🥑',
-  'eggplant': '🍆',
-  'potato': '🥔',
-  'carrot': '🥕',
-  'ear_of_corn': '🌽',
-  'hot_pepper': '🌶️',
-  'bell_pepper': '🫑',
-  'cucumber': '🥒',
-  'leafy_green': '🥬',
-  'broccoli': '🥦',
-  'garlic': '🧄',
-  'onion': '🧅',
-  'mushroom': '🍄',
-  'peanuts': '🥜',
-  'chestnut': '🌰',
-  'bread': '🍞',
-  'croissant': '🥐',
-  'baguette_bread': '🥖',
-  'flatbread': '🫓',
-  'pretzel': '🥨',
-  'bagel': '🥯',
-  'pancakes': '🥞',
-  'waffle': '🧇',
-  'cheese_wedge': '🧀',
-  'meat_on_bone': '🍖',
-  'poultry_leg': '🍗',
-  'cut_of_meat': '🥩',
-  'bacon': '🥓',
-  'hamburger': '🍔',
-  'french_fries': '🍟',
-  'pizza': '🍕',
-  'hot_dog': '🌭',
-  'sandwich': '🥪',
-  'taco': '🌮',
-  'burrito': '🌯',
-  'tamale': '🫔',
-  'stuffed_flatbread': '🥙',
-  'falafel': '🧆',
-  'egg': '🥚',
-  'cooking': '🍳',
-  'shallow_pan_of_food': '🥘',
-  'pot_of_food': '🍲',
-  'fondue': '🫕',
-  'bowl_with_spoon': '🥣',
-  'green_salad': '🥗',
-  'popcorn': '🍿',
-  'butter': '🧈',
-  'salt': '🧂',
-  'canned_food': '🥫',
-  'bento_box': '🍱',
-  'rice_cracker': '🍘',
-  'rice_ball': '🍙',
-  'cooked_rice': '🍚',
-  'curry_rice': '🍛',
-  'steaming_bowl': '🍜',
-  'spaghetti': '🍝',
-  'roasted_sweet_potato': '🍠',
-  'oden': '🍢',
-  'sushi': '🍣',
-  'fried_shrimp': '🍤',
-  'fish_cake_with_swirl': '🍥',
-  'moon_cake': '🥮',
-  'dango': '🍡',
-  'dumpling': '🥟',
-  'fortune_cookie': '🥠',
-  'takeout_box': '🥡',
-  'crab': '🦀',
-  'lobster': '🦞',
-  'shrimp': '🦐',
-  'squid': '🦑',
-  'oyster': '🦪',
-  'soft_ice_cream': '🍦',
-  'shaved_ice': '🍧',
-  'ice_cream': '🍨',
-  'doughnut': '🍩',
-  'cookie': '🍪',
-  'birthday_cake': '🎂',
-  'shortcake': '🍰',
-  'cupcake': '🧁',
-  'pie': '🥧',
-  'chocolate_bar': '🍫',
-  'candy': '🍬',
-  'lollipop': '🍭',
-  'custard': '🍮',
-  'honey_pot': '🍯',
-  'baby_bottle': '🍼',
-  'glass_of_milk': '🥛',
-  'hot_beverage': '☕',
-  'teapot': '🫖',
-  'teacup_without_handle': '🍵',
-  'sake': '🍶',
-  'bottle_with_popping_cork': '🍾',
-  'wine_glass': '🍷',
-  'cocktail_glass': '🍸',
-  'tropical_drink': '🍹',
-  'beer_mug': '🍺',
-  'clinking_beer_mugs': '🍻',
-  'clinking_glasses': '🥂',
-  'tumbler_glass': '🥃',
-  'cup_with_straw': '🥤',
-  'bubble_tea': '🧋',
-  'beverage_box': '🧃',
-  'mate': '🧉',
-  'ice': '🧊',
-  'chopsticks': '🥢',
-  'fork_and_knife_with_plate': '🍽️',
-  'fork_and_knife': '🍴',
-  'spoon': '🥄',
-  'kitchen_knife': '🔪',
-  'amphora': '🏺',
-  'globe_showing_europe_africa': '🌍',
-  'globe_showing_americas': '🌎',
-  'globe_showing_asia_australia': '🌏',
-  'globe_with_meridians': '🌐',
-  'world_map': '🗺️',
-  'map_of_japan': '🗾',
-  'compass': '🧭',
-  'snow_capped_mountain': '🏔️',
-  'mountain': '⛰️',
-  'volcano': '🌋',
-  'mount_fuji': '🗻',
-  'camping': '🏕️',
-  'beach_with_umbrella': '🏖️',
-  'desert': '🏜️',
-  'desert_island': '🏝️',
-  'national_park': '🏞️',
-  'stadium': '🏟️',
-  'classical_building': '🏛️',
-  'building_construction': '🏗️',
-  'brick': '🧱',
-  'rock': '🪨',
-  'wood': '🪵',
-  'hut': '🛖',
-  'houses': '🏘️',
-  'derelict_house': '🏚️',
-  'house': '🏠',
-  'house_with_garden': '🏡',
-  'office_building': '🏢',
-  'japanese_post_office': '🏣',
-  'post_office': '🏤',
-  'hospital': '🏥',
-  'bank': '🏦',
-  'hotel': '🏨',
-  'love_hotel': '🏩',
-  'convenience_store': '🏪',
-  'school': '🏫',
-  'department_store': '🏬',
-  'factory': '🏭',
-  'japanese_castle': '🏯',
-  'castle': '🏰',
-  'wedding': '💒',
-  'tokyo_tower': '🗼',
-  'statue_of_liberty': '🗽',
-  'church': '⛪',
-  'mosque': '🕌',
-  'hindu_temple': '🛕',
-  'synagogue': '🕍',
-  'shinto_shrine': '⛩️',
-  'kaaba': '🕋',
-  'fountain': '⛲',
-  'tent': '⛺',
-  'foggy': '🌁',
-  'night_with_stars': '🌃',
-  'cityscape': '🏙️',
-  'sunrise_over_mountains': '🌄',
-  'sunrise': '🌅',
-  'cityscape_at_dusk': '🌆',
-  'sunset': '🌇',
-  'bridge_at_night': '🌉',
-  'hot_springs': '♨️',
-  'carousel_horse': '🎠',
-  'ferris_wheel': '🎡',
-  'roller_coaster': '🎢',
-  'barber_pole': '💈',
-  'circus_tent': '🎪',
-  'locomotive': '🚂',
-  'railway_car': '🚃',
-  'high_speed_train': '🚄',
-  'bullet_train': '🚅',
-  'train': '🚆',
-  'metro': '🚇',
-  'light_rail': '🚈',
-  'station': '🚉',
-  'tram': '🚊',
-  'monorail': '🚝',
-  'mountain_railway': '🚞',
-  'tram_car': '🚋',
-  'bus': '🚌',
-  'oncoming_bus': '🚍',
-  'trolleybus': '🚎',
-  'minibus': '🚐',
-  'ambulance': '🚑',
-  'fire_engine': '🚒',
-  'police_car': '🚓',
-  'oncoming_police_car': '🚔',
-  'taxi': '🚕',
-  'oncoming_taxi': '🚖',
-  'automobile': '🚗',
-  'oncoming_automobile': '🚘',
-  'sport_utility_vehicle': '🚙',
-  'pickup_truck': '🛻',
-  'delivery_truck': '🚚',
-  'articulated_lorry': '🚛',
-  'tractor': '🚜',
-  'racing_car': '🏎️',
-  'motorcycle': '🏍️',
-  'motor_scooter': '🛵',
-  'manual_wheelchair': '🦽',
-  'motorized_wheelchair': '🦼',
-  'auto_rickshaw': '🛺',
-  'bicycle': '🚲',
-  'kick_scooter': '🛴',
-  'skateboard': '🛹',
-  'roller_skate': '🛼',
-  'bus_stop': '🚏',
-  'motorway': '🛣️',
-  'railway_track': '🛤️',
-  'oil_drum': '🛢️',
-  'fuel_pump': '⛽',
-  'police_car_light': '🚨',
-  'horizontal_traffic_light': '🚥',
-  'vertical_traffic_light': '🚦',
-  'stop_sign': '🛑',
-  'construction': '🚧',
-  'anchor': '⚓',
-  'sailboat': '⛵',
-  'canoe': '🛶',
-  'speedboat': '🚤',
-  'passenger_ship': '🛳️',
-  'ferry': '⛴️',
-  'motor_boat': '🛥️',
-  'ship': '🚢',
-  'airplane': '✈️',
-  'small_airplane': '🛩️',
-  'airplane_departure': '🛫',
-  'airplane_arrival': '🛬',
-  'parachute': '🪂',
-  'seat': '💺',
-  'helicopter': '🚁',
-  'suspension_railway': '🚟',
-  'mountain_cableway': '🚠',
-  'aerial_tramway': '🚡',
-  'satellite': '🛰️',
-  'rocket': '🚀',
-  'flying_saucer': '🛸',
-  'bellhop_bell': '🛎️',
-  'luggage': '🧳',
-  'hourglass_done': '⌛',
-  'hourglass_not_done': '⏳',
-  'watch': '⌚',
-  'alarm_clock': '⏰',
-  'stopwatch': '⏱️',
-  'timer_clock': '⏲️',
-  'mantelpiece_clock': '🕰️',
-  'twelve_o_clock': '🕛',
-  'twelve_thirty': '🕧',
-  'one_o_clock': '🕐',
-  'one_thirty': '🕜',
-  'two_o_clock': '🕑',
-  'two_thirty': '🕝',
-  'three_o_clock': '🕒',
-  'three_thirty': '🕞',
-  'four_o_clock': '🕓',
-  'four_thirty': '🕟',
-  'five_o_clock': '🕔',
-  'five_thirty': '🕠',
-  'six_o_clock': '🕕',
-  'six_thirty': '🕡',
-  'seven_o_clock': '🕖',
-  'seven_thirty': '🕢',
-  'eight_o_clock': '🕗',
-  'eight_thirty': '🕣',
-  'nine_o_clock': '🕘',
-  'nine_thirty': '🕤',
-  'ten_o_clock': '🕙',
-  'ten_thirty': '🕥',
-  'eleven_o_clock': '🕚',
-  'eleven_thirty': '🕦',
-  'new_moon': '🌑',
-  'waxing_crescent_moon': '🌒',
-  'first_quarter_moon': '🌓',
-  'waxing_gibbous_moon': '🌔',
-  'full_moon': '🌕',
-  'waning_gibbous_moon': '🌖',
-  'last_quarter_moon': '🌗',
-  'waning_crescent_moon': '🌘',
-  'crescent_moon': '🌙',
-  'new_moon_face': '🌚',
-  'first_quarter_moon_face': '🌛',
-  'last_quarter_moon_face': '🌜',
-  'thermometer': '🌡️',
-  'sun': '☀️',
-  'full_moon_face': '🌝',
-  'sun_with_face': '🌞',
-  'ringed_planet': '🪐',
-  'star': '⭐',
-  'glowing_star': '🌟',
-  'shooting_star': '🌠',
-  'milky_way': '🌌',
-  'cloud': '☁️',
-  'sun_behind_cloud': '⛅',
-  'cloud_with_lightning_and_rain': '⛈️',
-  'sun_behind_small_cloud': '🌤️',
-  'sun_behind_large_cloud': '🌥️',
-  'sun_behind_rain_cloud': '🌦️',
-  'cloud_with_rain': '🌧️',
-  'cloud_with_snow': '🌨️',
-  'cloud_with_lightning': '🌩️',
-  'tornado': '🌪️',
-  'fog': '🌫️',
-  'wind_face': '🌬️',
-  'cyclone': '🌀',
-  'rainbow': '🌈',
-  'closed_umbrella': '🌂',
-  'umbrella': '☂️',
-  'umbrella_with_rain_drops': '☔',
-  'umbrella_on_ground': '⛱️',
-  'high_voltage': '⚡',
-  'snowflake': '❄️',
-  'snowman': '☃️',
-  'snowman_without_snow': '⛄',
-  'comet': '☄️',
-  'fire': '🔥',
-  'droplet': '💧',
-  'water_wave': '🌊',
-  'jack_o_lantern': '🎃',
-  'christmas_tree': '🎄',
-  'fireworks': '🎆',
-  'sparkler': '🎇',
-  'firecracker': '🧨',
-  'sparkles': '✨',
-  'balloon': '🎈',
-  'party_popper': '🎉',
-  'confetti_ball': '🎊',
-  'tanabata_tree': '🎋',
-  'pine_decoration': '🎍',
-  'japanese_dolls': '🎎',
-  'carp_streamer': '🎏',
-  'wind_chime': '🎐',
-  'moon_viewing_ceremony': '🎑',
-  'red_envelope': '🧧',
-  'ribbon': '🎀',
-  'wrapped_gift': '🎁',
-  'reminder_ribbon': '🎗️',
-  'admission_tickets': '🎟️',
-  'ticket': '🎫',
-  'military_medal': '🎖️',
-  'trophy': '🏆',
-  'sports_medal': '🏅',
-  '1st_place_medal': '🥇',
-  '2nd_place_medal': '🥈',
-  '3rd_place_medal': '🥉',
-  'soccer_ball': '⚽',
-  'baseball': '⚾',
-  'softball': '🥎',
-  'basketball': '🏀',
-  'volleyball': '🏐',
-  'american_football': '🏈',
-  'rugby_football': '🏉',
-  'tennis': '🎾',
-  'flying_disc': '🥏',
-  'bowling': '🎳',
-  'cricket_game': '🏏',
-  'field_hockey': '🏑',
-  'ice_hockey': '🏒',
-  'lacrosse': '🥍',
-  'ping_pong': '🏓',
-  'badminton': '🏸',
-  'boxing_glove': '🥊',
-  'martial_arts_uniform': '🥋',
-  'goal_net': '🥅',
-  'flag_in_hole': '⛳',
-  'ice_skate': '⛸️',
-  'fishing_pole': '🎣',
-  'diving_mask': '🤿',
-  'running_shirt': '🎽',
-  'skis': '🎿',
-  'sled': '🛷',
-  'curling_stone': '🥌',
-  'bullseye': '🎯',
-  'yo_yo': '🪀',
-  'kite': '🪁',
-  'pool_8_ball': '🎱',
-  'crystal_ball': '🔮',
-  'magic_wand': '🪄',
-  'nazar_amulet': '🧿',
-  'video_game': '🎮',
-  'joystick': '🕹️',
-  'slot_machine': '🎰',
-  'game_die': '🎲',
-  'puzzle_piece': '🧩',
-  'teddy_bear': '🧸',
-  'pinata': '🪅',
-  'nesting_dolls': '🪆',
-  'spade_suit': '♠️',
-  'heart_suit': '♥️',
-  'diamond_suit': '♦️',
-  'club_suit': '♣️',
-  'chess_pawn': '♟️',
-  'joker': '🃏',
-  'mahjong_red_dragon': '🀄',
-  'flower_playing_cards': '🎴',
-  'performing_arts': '🎭',
-  'framed_picture': '🖼️',
-  'artist_palette': '🎨',
-  'thread': '🧵',
-  'sewing_needle': '🪡',
-  'yarn': '🧶',
-  'knot': '🪢',
-  'glasses': '👓',
-  'sunglasses': '🕶️',
-  'goggles': '🥽',
-  'lab_coat': '🥼',
-  'safety_vest': '🦺',
-  'necktie': '👔',
-  't_shirt': '👕',
-  'jeans': '👖',
-  'scarf': '🧣',
-  'gloves': '🧤',
-  'coat': '🧥',
-  'socks': '🧦',
-  'dress': '👗',
-  'kimono': '👘',
-  'sari': '🥻',
-  'one_piece_swimsuit': '🩱',
-  'briefs': '🩲',
-  'shorts': '🩳',
-  'bikini': '👙',
-  'woman_s_clothes': '👚',
-  'purse': '👛',
-  'handbag': '👜',
-  'clutch_bag': '👝',
-  'shopping_bags': '🛍️',
-  'backpack': '🎒',
-  'thong_sandal': '🩴',
-  'man_s_shoe': '👞',
-  'running_shoe': '👟',
-  'hiking_boot': '🥾',
-  'flat_shoe': '🥿',
-  'high_heeled_shoe': '👠',
-  'woman_s_sandal': '👡',
-  'ballet_shoes': '🩰',
-  'woman_s_boot': '👢',
-  'crown': '👑',
-  'woman_s_hat': '👒',
-  'top_hat': '🎩',
-  'graduation_cap': '🎓',
-  'billed_cap': '🧢',
-  'military_helmet': '🪖',
-  'rescue_worker_s_helmet': '⛑️',
-  'prayer_beads': '📿',
-  'lipstick': '💄',
-  'ring': '💍',
-  'gem_stone': '💎',
-  'muted_speaker': '🔇',
-  'speaker_low_volume': '🔈',
-  'speaker_medium_volume': '🔉',
-  'speaker_high_volume': '🔊',
-  'loudspeaker': '📢',
-  'megaphone': '📣',
-  'postal_horn': '📯',
-  'bell': '🔔',
-  'bell_with_slash': '🔕',
-  'musical_score': '🎼',
-  'musical_note': '🎵',
-  'musical_notes': '🎶',
-  'studio_microphone': '🎙️',
-  'level_slider': '🎚️',
-  'control_knobs': '🎛️',
-  'microphone': '🎤',
-  'headphone': '🎧',
-  'radio': '📻',
-  'saxophone': '🎷',
-  'accordion': '🪗',
-  'guitar': '🎸',
-  'musical_keyboard': '🎹',
-  'trumpet': '🎺',
-  'violin': '🎻',
-  'banjo': '🪕',
-  'drum': '🥁',
-  'long_drum': '🪘',
-  'mobile_phone': '📱',
-  'mobile_phone_with_arrow': '📲',
-  'telephone': '☎️',
-  'telephone_receiver': '📞',
-  'pager': '📟',
-  'fax_machine': '📠',
-  'battery': '🔋',
-  'electric_plug': '🔌',
-  'laptop': '💻',
-  'desktop_computer': '🖥️',
-  'printer': '🖨️',
-  'keyboard': '⌨️',
-  'computer_mouse': '🖱️',
-  'trackball': '🖲️',
-  'computer_disk': '💽',
-  'floppy_disk': '💾',
-  'optical_disk': '💿',
-  'dvd': '📀',
-  'abacus': '🧮',
-  'movie_camera': '🎥',
-  'film_frames': '🎞️',
-  'film_projector': '📽️',
-  'clapper_board': '🎬',
-  'television': '📺',
-  'camera': '📷',
-  'camera_with_flash': '📸',
-  'video_camera': '📹',
-  'videocassette': '📼',
-  'magnifying_glass_tilted_left': '🔍',
-  'magnifying_glass_tilted_right': '🔎',
-  'candle': '🕯️',
-  'light_bulb': '💡',
-  'flashlight': '🔦',
-  'red_paper_lantern': '🏮',
-  'diya_lamp': '🪔',
-  'notebook_with_decorative_cover': '📔',
-  'closed_book': '📕',
-  'open_book': '📖',
-  'green_book': '📗',
-  'blue_book': '📘',
-  'orange_book': '📙',
-  'books': '📚',
-  'notebook': '📓',
-  'ledger': '📒',
-  'page_with_curl': '📃',
-  'scroll': '📜',
-  'page_facing_up': '📄',
-  'newspaper': '📰',
-  'rolled_up_newspaper': '🗞️',
-  'bookmark_tabs': '📑',
-  'bookmark': '🔖',
-  'label': '🏷️',
-  'money_bag': '💰',
-  'coin': '🪙',
-  'yen_banknote': '💴',
-  'dollar_banknote': '💵',
-  'euro_banknote': '💶',
-  'pound_banknote': '💷',
-  'money_with_wings': '💸',
-  'credit_card': '💳',
-  'receipt': '🧾',
-  'chart_increasing_with_yen': '💹',
-  'envelope': '✉️',
-  'e_mail': '📧',
-  'incoming_envelope': '📨',
-  'envelope_with_arrow': '📩',
-  'outbox_tray': '📤',
-  'inbox_tray': '📥',
-  'package': '📦',
-  'closed_mailbox_with_raised_flag': '📫',
-  'closed_mailbox_with_lowered_flag': '📪',
-  'open_mailbox_with_raised_flag': '📬',
-  'open_mailbox_with_lowered_flag': '📭',
-  'postbox': '📮',
-  'ballot_box_with_ballot': '🗳️',
-  'pencil': '✏️',
-  'black_nib': '✒️',
-  'fountain_pen': '🖋️',
-  'pen': '🖊️',
-  'paintbrush': '🖌️',
-  'crayon': '🖍️',
-  'memo': '📝',
-  'briefcase': '💼',
-  'file_folder': '📁',
-  'open_file_folder': '📂',
-  'card_index_dividers': '🗂️',
-  'calendar': '📅',
-  'tear_off_calendar': '📆',
-  'spiral_notepad': '🗒️',
-  'spiral_calendar': '🗓️',
-  'card_index': '📇',
-  'chart_increasing': '📈',
-  'chart_decreasing': '📉',
-  'bar_chart': '📊',
-  'clipboard': '📋',
-  'pushpin': '📌',
-  'round_pushpin': '📍',
-  'paperclip': '📎',
-  'linked_paperclips': '🖇️',
-  'straight_ruler': '📏',
-  'triangular_ruler': '📐',
-  'scissors': '✂️',
-  'card_file_box': '🗃️',
-  'file_cabinet': '🗄️',
-  'wastebasket': '🗑️',
-  'locked': '🔒',
-  'unlocked': '🔓',
-  'locked_with_pen': '🔏',
-  'locked_with_key': '🔐',
-  'key': '🔑',
-  'old_key': '🗝️',
-  'hammer': '🔨',
-  'axe': '🪓',
-  'pick': '⛏️',
-  'hammer_and_pick': '⚒️',
-  'hammer_and_wrench': '🛠️',
-  'dagger': '🗡️',
-  'crossed_swords': '⚔️',
-  'water_pistol': '🔫',
-  'boomerang': '🪃',
-  'bow_and_arrow': '🏹',
-  'shield': '🛡️',
-  'carpentry_saw': '🪚',
-  'wrench': '🔧',
-  'screwdriver': '🪛',
-  'nut_and_bolt': '🔩',
-  'gear': '⚙️',
-  'clamp': '🗜️',
-  'balance_scale': '⚖️',
-  'white_cane': '🦯',
-  'link': '🔗',
-  'chains': '⛓️',
-  'hook': '🪝',
-  'toolbox': '🧰',
-  'magnet': '🧲',
-  'ladder': '🪜',
-  'alembic': '⚗️',
-  'test_tube': '🧪',
-  'petri_dish': '🧫',
-  'dna': '🧬',
-  'microscope': '🔬',
-  'telescope': '🔭',
-  'satellite_antenna': '📡',
-  'syringe': '💉',
-  'drop_of_blood': '🩸',
-  'pill': '💊',
-  'adhesive_bandage': '🩹',
-  'stethoscope': '🩺',
-  'door': '🚪',
-  'elevator': '🛗',
-  'mirror': '🪞',
-  'window': '🪟',
-  'bed': '🛏️',
-  'couch_and_lamp': '🛋️',
-  'chair': '🪑',
-  'toilet': '🚽',
-  'plunger': '🪠',
-  'shower': '🚿',
-  'bathtub': '🛁',
-  'mouse_trap': '🪤',
-  'razor': '🪒',
-  'lotion_bottle': '🧴',
-  'safety_pin': '🧷',
-  'broom': '🧹',
-  'basket': '🧺',
-  'roll_of_paper': '🧻',
-  'bucket': '🪣',
-  'soap': '🧼',
-  'toothbrush': '🪥',
-  'sponge': '🧽',
-  'fire_extinguisher': '🧯',
-  'shopping_cart': '🛒',
-  'cigarette': '🚬',
-  'coffin': '⚰️',
-  'headstone': '🪦',
-  'funeral_urn': '⚱️',
-  'moai': '🗿',
-  'placard': '🪧',
-  'atm_sign': '🏧',
-  'litter_in_bin_sign': '🚮',
-  'potable_water': '🚰',
-  'wheelchair_symbol': '♿',
-  'men_s_room': '🚹',
-  'women_s_room': '🚺',
-  'restroom': '🚻',
-  'baby_symbol': '🚼',
-  'water_closet': '🚾',
-  'passport_control': '🛂',
-  'customs': '🛃',
-  'baggage_claim': '🛄',
-  'left_luggage': '🛅',
-  'warning': '⚠️',
-  'children_crossing': '🚸',
-  'no_entry': '⛔',
-  'prohibited': '🚫',
-  'no_bicycles': '🚳',
-  'no_smoking': '🚭',
-  'no_littering': '🚯',
-  'non_potable_water': '🚱',
-  'no_pedestrians': '🚷',
-  'no_mobile_phones': '📵',
-  'no_one_under_eighteen': '🔞',
-  'radioactive': '☢️',
-  'biohazard': '☣️',
-  'up_arrow': '⬆️',
-  'up_right_arrow': '↗️',
-  'right_arrow': '➡️',
-  'down_right_arrow': '↘️',
-  'down_arrow': '⬇️',
-  'down_left_arrow': '↙️',
-  'left_arrow': '⬅️',
-  'up_left_arrow': '↖️',
-  'up_down_arrow': '↕️',
-  'left_right_arrow': '↔️',
-  'right_arrow_curving_left': '↩️',
-  'left_arrow_curving_right': '↪️',
-  'right_arrow_curving_up': '⤴️',
-  'right_arrow_curving_down': '⤵️',
-  'clockwise_vertical_arrows': '🔃',
-  'counterclockwise_arrows_button': '🔄',
-  'back_arrow': '🔙',
-  'end_arrow': '🔚',
-  'on_arrow': '🔛',
-  'soon_arrow': '🔜',
-  'top_arrow': '🔝',
-  'place_of_worship': '🛐',
-  'atom_symbol': '⚛️',
-  'om': '🕉️',
-  'star_of_david': '✡️',
-  'wheel_of_dharma': '☸️',
-  'yin_yang': '☯️',
-  'latin_cross': '✝️',
-  'orthodox_cross': '☦️',
-  'star_and_crescent': '☪️',
-  'peace_symbol': '☮️',
-  'menorah': '🕎',
-  'dotted_six_pointed_star': '🔯',
-  'aries': '♈',
-  'taurus': '♉',
-  'gemini': '♊',
-  'cancer': '♋',
-  'leo': '♌',
-  'virgo': '♍',
-  'libra': '♎',
-  'scorpio': '♏',
-  'sagittarius': '♐',
-  'capricorn': '♑',
-  'aquarius': '♒',
-  'pisces': '♓',
-  'ophiuchus': '⛎',
-  'shuffle_tracks_button': '🔀',
-  'repeat_button': '🔁',
-  'repeat_single_button': '🔂',
-  'play_button': '▶️',
-  'fast_forward_button': '⏩',
-  'next_track_button': '⏭️',
-  'play_or_pause_button': '⏯️',
-  'reverse_button': '◀️',
-  'fast_reverse_button': '⏪',
-  'last_track_button': '⏮️',
-  'upwards_button': '🔼',
-  'fast_up_button': '⏫',
-  'downwards_button': '🔽',
-  'fast_down_button': '⏬',
-  'pause_button': '⏸️',
-  'stop_button': '⏹️',
-  'record_button': '⏺️',
-  'eject_button': '⏏️',
-  'cinema': '🎦',
-  'dim_button': '🔅',
-  'bright_button': '🔆',
-  'antenna_bars': '📶',
-  'vibration_mode': '📳',
-  'mobile_phone_off': '📴',
-  'female_sign': '♀️',
-  'male_sign': '♂️',
-  'transgender_symbol': '⚧️',
-  'multiply': '✖️',
-  'plus': '➕',
-  'minus': '➖',
-  'divide': '➗',
-  'infinity': '♾️',
-  'double_exclamation_mark': '‼️',
-  'exclamation_question_mark': '⁉️',
-  'red_question_mark': '❓',
-  'white_question_mark': '❔',
-  'white_exclamation_mark': '❕',
-  'red_exclamation_mark': '❗',
-  'wavy_dash': '〰️',
-  'currency_exchange': '💱',
-  'heavy_dollar_sign': '💲',
-  'medical_symbol': '⚕️',
-  'recycling_symbol': '♻️',
-  'fleur_de_lis': '⚜️',
-  'trident_emblem': '🔱',
-  'name_badge': '📛',
-  'japanese_symbol_for_beginner': '🔰',
-  'hollow_red_circle': '⭕',
-  'check_mark_button': '✅',
-  'check_box_with_check': '☑️',
-  'check_mark': '✔️',
-  'cross_mark': '❌',
-  'cross_mark_button': '❎',
-  'curly_loop': '➰',
-  'double_curly_loop': '➿',
-  'part_alternation_mark': '〽️',
-  'eight_spoked_asterisk': '✳️',
-  'eight_pointed_star': '✴️',
-  'sparkle': '❇️',
-  'copyright': '©️',
-  'registered': '®️',
-  'trade_mark': '™️',
-  'keycap_': '*️⃣',
-  'keycap_0': '0️⃣',
-  'keycap_1': '1️⃣',
-  'keycap_2': '2️⃣',
-  'keycap_3': '3️⃣',
-  'keycap_4': '4️⃣',
-  'keycap_5': '5️⃣',
-  'keycap_6': '6️⃣',
-  'keycap_7': '7️⃣',
-  'keycap_8': '8️⃣',
-  'keycap_9': '9️⃣',
-  'keycap_10': '🔟',
-  'input_latin_uppercase': '🔠',
-  'input_latin_lowercase': '🔡',
-  'input_numbers': '🔢',
-  'input_symbols': '🔣',
-  'input_latin_letters': '🔤',
-  'a_button': '🅰️',
-  'ab_button': '🆎',
-  'b_button': '🅱️',
-  'cl_button': '🆑',
-  'cool_button': '🆒',
-  'free_button': '🆓',
-  'information': 'ℹ️',
-  'id_button': '🆔',
-  'circled_m': 'Ⓜ️',
-  'new_button': '🆕',
-  'ng_button': '🆖',
-  'o_button': '🅾️',
-  'ok_button': '🆗',
-  'p_button': '🅿️',
-  'sos_button': '🆘',
-  'up_button': '🆙',
-  'vs_button': '🆚',
-  'japanese_here_button': '🈁',
-  'japanese_service_charge_button': '🈂️',
-  'japanese_monthly_amount_button': '🈷️',
-  'japanese_not_free_of_charge_button': '🈶',
-  'japanese_reserved_button': '🈯',
-  'japanese_bargain_button': '🉐',
-  'japanese_discount_button': '🈹',
-  'japanese_free_of_charge_button': '🈚',
-  'japanese_prohibited_button': '🈲',
-  'japanese_acceptable_button': '🉑',
-  'japanese_application_button': '🈸',
-  'japanese_passing_grade_button': '🈴',
-  'japanese_vacancy_button': '🈳',
-  'japanese_congratulations_button': '㊗️',
-  'japanese_secret_button': '㊙️',
-  'japanese_open_for_business_button': '🈺',
-  'japanese_no_vacancy_button': '🈵',
-  'red_circle': '🔴',
-  'orange_circle': '🟠',
-  'yellow_circle': '🟡',
-  'green_circle': '🟢',
-  'blue_circle': '🔵',
-  'purple_circle': '🟣',
-  'brown_circle': '🟤',
-  'black_circle': '⚫',
-  'white_circle': '⚪',
-  'red_square': '🟥',
-  'orange_square': '🟧',
-  'yellow_square': '🟨',
-  'green_square': '🟩',
-  'blue_square': '🟦',
-  'purple_square': '🟪',
-  'brown_square': '🟫',
-  'black_large_square': '⬛',
-  'white_large_square': '⬜',
-  'black_medium_square': '◼️',
-  'white_medium_square': '◻️',
-  'black_medium_small_square': '◾',
-  'white_medium_small_square': '◽',
-  'black_small_square': '▪️',
-  'white_small_square': '▫️',
-  'large_orange_diamond': '🔶',
-  'large_blue_diamond': '🔷',
-  'small_orange_diamond': '🔸',
-  'small_blue_diamond': '🔹',
-  'red_triangle_pointed_up': '🔺',
-  'red_triangle_pointed_down': '🔻',
-  'diamond_with_a_dot': '💠',
-  'radio_button': '🔘',
-  'white_square_button': '🔳',
-  'black_square_button': '🔲',
-  'chequered_flag': '🏁',
-  'triangular_flag': '🚩',
-  'crossed_flags': '🎌',
-  'black_flag': '🏴',
-  'white_flag': '🏳️',
-  'rainbow_flag': '🏳️‍🌈',
-  'transgender_flag': '🏳️‍⚧️',
-  'pirate_flag': '🏴‍☠️',
-  'flag_ascension_island': '🇦🇨',
-  'flag_andorra': '🇦🇩',
-  'flag_united_arab_emirates': '🇦🇪',
-  'flag_afghanistan': '🇦🇫',
-  'flag_antigua_barbuda': '🇦🇬',
-  'flag_anguilla': '🇦🇮',
-  'flag_albania': '🇦🇱',
-  'flag_armenia': '🇦🇲',
-  'flag_angola': '🇦🇴',
-  'flag_antarctica': '🇦🇶',
-  'flag_argentina': '🇦🇷',
-  'flag_american_samoa': '🇦🇸',
-  'flag_austria': '🇦🇹',
-  'flag_australia': '🇦🇺',
-  'flag_aruba': '🇦🇼',
-  'flag_aland_islands': '🇦🇽',
-  'flag_azerbaijan': '🇦🇿',
-  'flag_bosnia_herzegovina': '🇧🇦',
-  'flag_barbados': '🇧🇧',
-  'flag_bangladesh': '🇧🇩',
-  'flag_belgium': '🇧🇪',
-  'flag_burkina_faso': '🇧🇫',
-  'flag_bulgaria': '🇧🇬',
-  'flag_bahrain': '🇧🇭',
-  'flag_burundi': '🇧🇮',
-  'flag_benin': '🇧🇯',
-  'flag_st_barthelemy': '🇧🇱',
-  'flag_bermuda': '🇧🇲',
-  'flag_brunei': '🇧🇳',
-  'flag_bolivia': '🇧🇴',
-  'flag_caribbean_netherlands': '🇧🇶',
-  'flag_brazil': '🇧🇷',
-  'flag_bahamas': '🇧🇸',
-  'flag_bhutan': '🇧🇹',
-  'flag_bouvet_island': '🇧🇻',
-  'flag_botswana': '🇧🇼',
-  'flag_belarus': '🇧🇾',
-  'flag_belize': '🇧🇿',
-  'flag_canada': '🇨🇦',
-  'flag_cocos_islands': '🇨🇨',
-  'flag_congo_kinshasa': '🇨🇩',
-  'flag_central_african_republic': '🇨🇫',
-  'flag_congo_brazzaville': '🇨🇬',
-  'flag_switzerland': '🇨🇭',
-  'flag_cote_d_ivoire': '🇨🇮',
-  'flag_cook_islands': '🇨🇰',
-  'flag_chile': '🇨🇱',
-  'flag_cameroon': '🇨🇲',
-  'flag_china': '🇨🇳',
-  'flag_colombia': '🇨🇴',
-  'flag_clipperton_island': '🇨🇵',
-  'flag_costa_rica': '🇨🇷',
-  'flag_cuba': '🇨🇺',
-  'flag_cape_verde': '🇨🇻',
-  'flag_curacao': '🇨🇼',
-  'flag_christmas_island': '🇨🇽',
-  'flag_cyprus': '🇨🇾',
-  'flag_czechia': '🇨🇿',
-  'flag_germany': '🇩🇪',
-  'flag_diego_garcia': '🇩🇬',
-  'flag_djibouti': '🇩🇯',
-  'flag_denmark': '🇩🇰',
-  'flag_dominica': '🇩🇲',
-  'flag_dominican_republic': '🇩🇴',
-  'flag_algeria': '🇩🇿',
-  'flag_ceuta_melilla': '🇪🇦',
-  'flag_ecuador': '🇪🇨',
-  'flag_estonia': '🇪🇪',
-  'flag_egypt': '🇪🇬',
-  'flag_western_sahara': '🇪🇭',
-  'flag_eritrea': '🇪🇷',
-  'flag_spain': '🇪🇸',
-  'flag_ethiopia': '🇪🇹',
-  'flag_european_union': '🇪🇺',
-  'flag_finland': '🇫🇮',
-  'flag_fiji': '🇫🇯',
-  'flag_falkland_islands': '🇫🇰',
-  'flag_micronesia': '🇫🇲',
-  'flag_faroe_islands': '🇫🇴',
-  'flag_france': '🇫🇷',
-  'flag_gabon': '🇬🇦',
-  'flag_united_kingdom': '🇬🇧',
-  'flag_grenada': '🇬🇩',
-  'flag_georgia': '🇬🇪',
-  'flag_french_guiana': '🇬🇫',
-  'flag_guernsey': '🇬🇬',
-  'flag_ghana': '🇬🇭',
-  'flag_gibraltar': '🇬🇮',
-  'flag_greenland': '🇬🇱',
-  'flag_gambia': '🇬🇲',
-  'flag_guinea': '🇬🇳',
-  'flag_guadeloupe': '🇬🇵',
-  'flag_equatorial_guinea': '🇬🇶',
-  'flag_greece': '🇬🇷',
-  'flag_south_georgia_south_sandwich_islands': '🇬🇸',
-  'flag_guatemala': '🇬🇹',
-  'flag_guam': '🇬🇺',
-  'flag_guinea_bissau': '🇬🇼',
-  'flag_guyana': '🇬🇾',
-  'flag_hong_kong_sar_china': '🇭🇰',
-  'flag_heard_mcdonald_islands': '🇭🇲',
-  'flag_honduras': '🇭🇳',
-  'flag_croatia': '🇭🇷',
-  'flag_haiti': '🇭🇹',
-  'flag_hungary': '🇭🇺',
-  'flag_canary_islands': '🇮🇨',
-  'flag_indonesia': '🇮🇩',
-  'flag_ireland': '🇮🇪',
-  'flag_israel': '🇮🇱',
-  'flag_isle_of_man': '🇮🇲',
-  'flag_india': '🇮🇳',
-  'flag_british_indian_ocean_territory': '🇮🇴',
-  'flag_iraq': '🇮🇶',
-  'flag_iran': '🇮🇷',
-  'flag_iceland': '🇮🇸',
-  'flag_italy': '🇮🇹',
-  'flag_jersey': '🇯🇪',
-  'flag_jamaica': '🇯🇲',
-  'flag_jordan': '🇯🇴',
-  'flag_japan': '🇯🇵',
-  'flag_kenya': '🇰🇪',
-  'flag_kyrgyzstan': '🇰🇬',
-  'flag_cambodia': '🇰🇭',
-  'flag_kiribati': '🇰🇮',
-  'flag_comoros': '🇰🇲',
-  'flag_st_kitts_nevis': '🇰🇳',
-  'flag_north_korea': '🇰🇵',
-  'flag_south_korea': '🇰🇷',
-  'flag_kuwait': '🇰🇼',
-  'flag_cayman_islands': '🇰🇾',
-  'flag_kazakhstan': '🇰🇿',
-  'flag_laos': '🇱🇦',
-  'flag_lebanon': '🇱🇧',
-  'flag_st_lucia': '🇱🇨',
-  'flag_liechtenstein': '🇱🇮',
-  'flag_sri_lanka': '🇱🇰',
-  'flag_liberia': '🇱🇷',
-  'flag_lesotho': '🇱🇸',
-  'flag_lithuania': '🇱🇹',
-  'flag_luxembourg': '🇱🇺',
-  'flag_latvia': '🇱🇻',
-  'flag_libya': '🇱🇾',
-  'flag_morocco': '🇲🇦',
-  'flag_monaco': '🇲🇨',
-  'flag_moldova': '🇲🇩',
-  'flag_montenegro': '🇲🇪',
-  'flag_st_martin': '🇲🇫',
-  'flag_madagascar': '🇲🇬',
-  'flag_marshall_islands': '🇲🇭',
-  'flag_north_macedonia': '🇲🇰',
-  'flag_mali': '🇲🇱',
-  'flag_myanmar': '🇲🇲',
-  'flag_mongolia': '🇲🇳',
-  'flag_macao_sar_china': '🇲🇴',
-  'flag_northern_mariana_islands': '🇲🇵',
-  'flag_martinique': '🇲🇶',
-  'flag_mauritania': '🇲🇷',
-  'flag_montserrat': '🇲🇸',
-  'flag_malta': '🇲🇹',
-  'flag_mauritius': '🇲🇺',
-  'flag_maldives': '🇲🇻',
-  'flag_malawi': '🇲🇼',
-  'flag_mexico': '🇲🇽',
-  'flag_malaysia': '🇲🇾',
-  'flag_mozambique': '🇲🇿',
-  'flag_namibia': '🇳🇦',
-  'flag_new_caledonia': '🇳🇨',
-  'flag_niger': '🇳🇪',
-  'flag_norfolk_island': '🇳🇫',
-  'flag_nigeria': '🇳🇬',
-  'flag_nicaragua': '🇳🇮',
-  'flag_netherlands': '🇳🇱',
-  'flag_norway': '🇳🇴',
-  'flag_nepal': '🇳🇵',
-  'flag_nauru': '🇳🇷',
-  'flag_niue': '🇳🇺',
-  'flag_new_zealand': '🇳🇿',
-  'flag_oman': '🇴🇲',
-  'flag_panama': '🇵🇦',
-  'flag_peru': '🇵🇪',
-  'flag_french_polynesia': '🇵🇫',
-  'flag_papua_new_guinea': '🇵🇬',
-  'flag_philippines': '🇵🇭',
-  'flag_pakistan': '🇵🇰',
-  'flag_poland': '🇵🇱',
-  'flag_st_pierre_miquelon': '🇵🇲',
-  'flag_pitcairn_islands': '🇵🇳',
-  'flag_puerto_rico': '🇵🇷',
-  'flag_palestinian_territories': '🇵🇸',
-  'flag_portugal': '🇵🇹',
-  'flag_palau': '🇵🇼',
-  'flag_paraguay': '🇵🇾',
-  'flag_qatar': '🇶🇦',
-  'flag_reunion': '🇷🇪',
-  'flag_romania': '🇷🇴',
-  'flag_serbia': '🇷🇸',
-  'flag_russia': '🇷🇺',
-  'flag_rwanda': '🇷🇼',
-  'flag_saudi_arabia': '🇸🇦',
-  'flag_solomon_islands': '🇸🇧',
-  'flag_seychelles': '🇸🇨',
-  'flag_sudan': '🇸🇩',
-  'flag_sweden': '🇸🇪',
-  'flag_singapore': '🇸🇬',
-  'flag_st_helena': '🇸🇭',
-  'flag_slovenia': '🇸🇮',
-  'flag_svalbard_jan_mayen': '🇸🇯',
-  'flag_slovakia': '🇸🇰',
-  'flag_sierra_leone': '🇸🇱',
-  'flag_san_marino': '🇸🇲',
-  'flag_senegal': '🇸🇳',
-  'flag_somalia': '🇸🇴',
-  'flag_suriname': '🇸🇷',
-  'flag_south_sudan': '🇸🇸',
-  'flag_sao_tome_principe': '🇸🇹',
-  'flag_el_salvador': '🇸🇻',
-  'flag_sint_maarten': '🇸🇽',
-  'flag_syria': '🇸🇾',
-  'flag_eswatini': '🇸🇿',
-  'flag_tristan_da_cunha': '🇹🇦',
-  'flag_turks_caicos_islands': '🇹🇨',
-  'flag_chad': '🇹🇩',
-  'flag_french_southern_territories': '🇹🇫',
-  'flag_togo': '🇹🇬',
-  'flag_thailand': '🇹🇭',
-  'flag_tajikistan': '🇹🇯',
-  'flag_tokelau': '🇹🇰',
-  'flag_timor_leste': '🇹🇱',
-  'flag_turkmenistan': '🇹🇲',
-  'flag_tunisia': '🇹🇳',
-  'flag_tonga': '🇹🇴',
-  'flag_turkey': '🇹🇷',
-  'flag_trinidad_tobago': '🇹🇹',
-  'flag_tuvalu': '🇹🇻',
-  'flag_taiwan': '🇹🇼',
-  'flag_tanzania': '🇹🇿',
-  'flag_ukraine': '🇺🇦',
-  'flag_uganda': '🇺🇬',
-  'flag_u_s_outlying_islands': '🇺🇲',
-  'flag_united_nations': '🇺🇳',
-  'flag_united_states': '🇺🇸',
-  'flag_uruguay': '🇺🇾',
-  'flag_uzbekistan': '🇺🇿',
-  'flag_vatican_city': '🇻🇦',
-  'flag_st_vincent_grenadines': '🇻🇨',
-  'flag_venezuela': '🇻🇪',
-  'flag_british_virgin_islands': '🇻🇬',
-  'flag_u_s_virgin_islands': '🇻🇮',
-  'flag_vietnam': '🇻🇳',
-  'flag_vanuatu': '🇻🇺',
-  'flag_wallis_futuna': '🇼🇫',
-  'flag_samoa': '🇼🇸',
-  'flag_kosovo': '🇽🇰',
-  'flag_yemen': '🇾🇪',
-  'flag_mayotte': '🇾🇹',
-  'flag_south_africa': '🇿🇦',
-  'flag_zambia': '🇿🇲',
-  'flag_zimbabwe': '🇿🇼',
-  'flag_england': '🏴󠁧󠁢󠁥󠁮󠁧󠁿',
-  'flag_scotland': '🏴󠁧󠁢󠁳󠁣󠁴󠁿',
-  'flag_wales': '🏴󠁧󠁢󠁷󠁬󠁳󠁿',
-};

+ 0 - 64
frontend/app_flowy/lib/workspace/application/markdown/src/extension_set.dart

@@ -1,64 +0,0 @@
-import 'block_parser.dart';
-import 'inline_parser.dart';
-
-/// ExtensionSets provide a simple grouping mechanism for common Markdown
-/// flavors.
-///
-/// For example, the [gitHubFlavored] set of syntax extensions allows users to
-/// output HTML from their Markdown in a similar fashion to GitHub's parsing.
-class ExtensionSet {
-  ExtensionSet(this.blockSyntaxes, this.inlineSyntaxes);
-
-  /// The [ExtensionSet.none] extension set renders Markdown similar to
-  /// [Markdown.pl].
-  ///
-  /// However, this set does not render _exactly_ the same as Markdown.pl;
-  /// rather it is more-or-less the CommonMark standard of Markdown, without
-  /// fenced code blocks, or inline HTML.
-  ///
-  /// [Markdown.pl]: http://daringfireball.net/projects/markdown/syntax
-  static final ExtensionSet none = ExtensionSet([], []);
-
-  /// The [commonMark] extension set is close to compliance with [CommonMark].
-  ///
-  /// [CommonMark]: http://commonmark.org/
-  static final ExtensionSet commonMark =
-      ExtensionSet([const FencedCodeBlockSyntax()], [InlineHtmlSyntax()]);
-
-  /// The [gitHubWeb] extension set renders Markdown similarly to GitHub.
-  ///
-  /// This is different from the [gitHubFlavored] extension set in that GitHub
-  /// actually renders HTML different from straight [GitHub flavored Markdown].
-  ///
-  /// (The only difference currently is that [gitHubWeb] renders headers with
-  /// linkable IDs.)
-  ///
-  /// [GitHub flavored Markdown]: https://github.github.com/gfm/
-  static final ExtensionSet gitHubWeb = ExtensionSet([
-    const FencedCodeBlockSyntax(),
-    const HeaderWithIdSyntax(),
-    const SetextHeaderWithIdSyntax(),
-    const TableSyntax()
-  ], [
-    InlineHtmlSyntax(),
-    StrikethroughSyntax(),
-    EmojiSyntax(),
-    AutolinkExtensionSyntax(),
-  ]);
-
-  /// The [gitHubFlavored] extension set is close to compliance with the [GitHub
-  /// flavored Markdown spec].
-  ///
-  /// [GitHub flavored Markdown]: https://github.github.com/gfm/
-  static final ExtensionSet gitHubFlavored = ExtensionSet([
-    const FencedCodeBlockSyntax(),
-    const TableSyntax()
-  ], [
-    InlineHtmlSyntax(),
-    StrikethroughSyntax(),
-    AutolinkExtensionSyntax(),
-  ]);
-
-  final List<BlockSyntax> blockSyntaxes;
-  final List<InlineSyntax> inlineSyntaxes;
-}

+ 0 - 121
frontend/app_flowy/lib/workspace/application/markdown/src/html_renderer.dart

@@ -1,121 +0,0 @@
-// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'ast.dart';
-import 'block_parser.dart';
-import 'document.dart';
-import 'extension_set.dart';
-import 'inline_parser.dart';
-
-/// Converts the given string of Markdown to HTML.
-String markdownToHtml(String markdown,
-    {Iterable<BlockSyntax>? blockSyntaxes,
-    Iterable<InlineSyntax>? inlineSyntaxes,
-    ExtensionSet? extensionSet,
-    Resolver? linkResolver,
-    Resolver? imageLinkResolver,
-    bool inlineOnly = false}) {
-  final document = Document(
-      blockSyntaxes: blockSyntaxes,
-      inlineSyntaxes: inlineSyntaxes,
-      extensionSet: extensionSet,
-      linkResolver: linkResolver,
-      imageLinkResolver: imageLinkResolver);
-
-  if (inlineOnly) {
-    return renderToHtml(document.parseInline(markdown)!);
-  }
-
-  // Replace windows line endings with unix line endings, and split.
-  final lines = markdown.replaceAll('\r\n', '\n').split('\n');
-
-  return '${renderToHtml(document.parseLines(lines))}\n';
-}
-
-/// Renders [nodes] to HTML.
-String renderToHtml(List<Node> nodes) => HtmlRenderer().render(nodes);
-
-/// Translates a parsed AST to HTML.
-class HtmlRenderer implements NodeVisitor {
-  HtmlRenderer();
-
-  static final _blockTags = RegExp('blockquote|h1|h2|h3|h4|h5|h6|hr|p|pre');
-
-  late StringBuffer buffer;
-  late Set<String> uniqueIds;
-
-  String render(List<Node> nodes) {
-    buffer = StringBuffer();
-    uniqueIds = <String>{};
-
-    for (final node in nodes) {
-      node.accept(this);
-    }
-
-    return buffer.toString();
-  }
-
-  @override
-  void visitText(Text text) {
-    buffer.write(text.text);
-  }
-
-  @override
-  bool visitElementBefore(Element element) {
-    // Hackish. Separate block-level elements with newlines.
-    if (buffer.isNotEmpty && _blockTags.firstMatch(element.tag) != null) {
-      buffer.write('\n');
-    }
-
-    buffer.write('<${element.tag}');
-
-    // Sort the keys so that we generate stable output.
-    final attributeNames = element.attributes.keys.toList()
-      ..sort((a, b) => a.compareTo(b));
-
-    for (final name in attributeNames) {
-      buffer.write(' $name="${element.attributes[name]}"');
-    }
-
-    // attach header anchor ids generated from text
-    if (element.generatedId != null) {
-      buffer.write(' id="${uniquifyId(element.generatedId!)}"');
-    }
-
-    if (element.isEmpty) {
-      // Empty element like <hr/>.
-      buffer.write(' />');
-
-      if (element.tag == 'br') {
-        buffer.write('\n');
-      }
-
-      return false;
-    } else {
-      buffer.write('>');
-      return true;
-    }
-  }
-
-  @override
-  void visitElementAfter(Element element) {
-    buffer.write('</${element.tag}>');
-  }
-
-  /// Uniquifies an id generated from text.
-  String uniquifyId(String id) {
-    if (!uniqueIds.contains(id)) {
-      uniqueIds.add(id);
-      return id;
-    }
-
-    var suffix = 2;
-    var suffixedId = '$id-$suffix';
-    while (uniqueIds.contains(suffixedId)) {
-      suffixedId = '$id-${suffix++}';
-    }
-    uniqueIds.add(suffixedId);
-    return suffixedId;
-  }
-}

+ 0 - 1270
frontend/app_flowy/lib/workspace/application/markdown/src/inline_parser.dart

@@ -1,1270 +0,0 @@
-// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'package:charcode/charcode.dart';
-
-import 'ast.dart';
-import 'document.dart';
-import 'emojis.dart';
-import 'util.dart';
-
-/// Maintains the internal state needed to parse inline span elements in
-/// Markdown.
-class InlineParser {
-  InlineParser(this.source, this.document) : _stack = <TagState>[] {
-    // User specified syntaxes are the first syntaxes to be evaluated.
-    syntaxes.addAll(document.inlineSyntaxes);
-
-    final documentHasCustomInlineSyntaxes = document.inlineSyntaxes
-        .any((s) => !document.extensionSet.inlineSyntaxes.contains(s));
-
-    // This first RegExp matches plain text to accelerate parsing. It's written
-    // so that it does not match any prefix of any following syntaxes. Most
-    // Markdown is plain text, so it's faster to match one RegExp per 'word'
-    // rather than fail to match all the following RegExps at each non-syntax
-    // character position.
-    if (documentHasCustomInlineSyntaxes) {
-      // We should be less aggressive in blowing past "words".
-      syntaxes.add(TextSyntax(r'[A-Za-z0-9]+(?=\s)'));
-    } else {
-      syntaxes.add(TextSyntax(r'[ \tA-Za-z0-9]*[A-Za-z0-9](?=\s)'));
-    }
-
-    syntaxes
-      ..addAll(_defaultSyntaxes)
-      // Custom link resolvers go after the generic text syntax.
-      ..insertAll(1, [
-        LinkSyntax(linkResolver: document.linkResolver),
-        ImageSyntax(linkResolver: document.imageLinkResolver)
-      ]);
-  }
-
-  static final List<InlineSyntax> _defaultSyntaxes =
-      List<InlineSyntax>.unmodifiable(<InlineSyntax>[
-    EmailAutolinkSyntax(),
-    AutolinkSyntax(),
-    LineBreakSyntax(),
-    LinkSyntax(),
-    ImageSyntax(),
-    // Allow any punctuation to be escaped.
-    EscapeSyntax(),
-    // "*" surrounded by spaces is left alone.
-    TextSyntax(r' \* '),
-    // "_" surrounded by spaces is left alone.
-    TextSyntax(r' _ '),
-    // Parse "**strong**" and "*emphasis*" tags.
-    TagSyntax(r'\*+', requiresDelimiterRun: true),
-    // Parse "__strong__" and "_emphasis_" tags.
-    TagSyntax(r'_+', requiresDelimiterRun: true),
-    CodeSyntax(),
-    // We will add the LinkSyntax once we know about the specific link resolver.
-  ]);
-
-  /// The string of Markdown being parsed.
-  final String source;
-
-  /// The Markdown document this parser is parsing.
-  final Document document;
-
-  final List<InlineSyntax> syntaxes = <InlineSyntax>[];
-
-  /// The current read position.
-  int pos = 0;
-
-  /// Starting position of the last unconsumed text.
-  int start = 0;
-
-  final List<TagState> _stack;
-
-  List<Node>? parse() {
-    // Make a fake top tag to hold the results.
-    _stack.add(TagState(0, 0, null, null));
-
-    while (!isDone) {
-      // See if any of the current tags on the stack match.  This takes
-      // priority over other possible matches.
-      if (_stack.reversed
-          .any((state) => state.syntax != null && state.tryMatch(this))) {
-        continue;
-      }
-
-      // See if the current text matches any defined markdown syntax.
-      if (syntaxes.any((syntax) => syntax.tryMatch(this))) {
-        continue;
-      }
-
-      // If we got here, it's just text.
-      advanceBy(1);
-    }
-
-    // Unwind any unmatched tags and get the results.
-    return _stack[0].close(this, null);
-  }
-
-  int charAt(int index) => source.codeUnitAt(index);
-
-  void writeText() {
-    writeTextRange(start, pos);
-    start = pos;
-  }
-
-  void writeTextRange(int start, int end) {
-    if (end <= start) {
-      return;
-    }
-
-    final text = source.substring(start, end);
-    final nodes = _stack.last.children;
-
-    // If the previous node is text too, just append.
-    if (nodes.isNotEmpty && nodes.last is Text) {
-      final textNode = nodes.last as Text;
-      nodes[nodes.length - 1] = Text('${textNode.text}$text');
-    } else {
-      nodes.add(Text(text));
-    }
-  }
-
-  /// Add [node] to the last [TagState] on the stack.
-  void addNode(Node node) {
-    _stack.last.children.add(node);
-  }
-
-  /// Push [state] onto the stack of [TagState]s.
-  void openTag(TagState state) => _stack.add(state);
-
-  bool get isDone => pos == source.length;
-
-  void advanceBy(int length) {
-    pos += length;
-  }
-
-  void consume(int length) {
-    pos += length;
-    start = pos;
-  }
-}
-
-/// Represents one kind of Markdown tag that can be parsed.
-abstract class InlineSyntax {
-  InlineSyntax(String pattern) : pattern = RegExp(pattern, multiLine: true);
-
-  final RegExp pattern;
-
-  /// Tries to match at the parser's current position.
-  ///
-  /// The parser's position can be overridden with [startMatchPos].
-  /// Returns whether or not the pattern successfully matched.
-  bool tryMatch(InlineParser parser, [int? startMatchPos]) {
-    startMatchPos ??= parser.pos;
-
-    final startMatch = pattern.matchAsPrefix(parser.source, startMatchPos);
-    if (startMatch == null) {
-      return false;
-    }
-
-    // Write any existing plain text up to this point.
-    parser.writeText();
-
-    if (onMatch(parser, startMatch)) {
-      parser.consume(startMatch[0]!.length);
-    }
-    return true;
-  }
-
-  /// Processes [match], adding nodes to [parser] and possibly advancing
-  /// [parser].
-  ///
-  /// Returns whether the caller should advance [parser] by `match[0].length`.
-  bool onMatch(InlineParser parser, Match match);
-}
-
-/// Represents a hard line break.
-class LineBreakSyntax extends InlineSyntax {
-  LineBreakSyntax() : super(r'(?:\\|  +)\n');
-
-  /// Create a void <br> element.
-  @override
-  bool onMatch(InlineParser parser, Match match) {
-    parser.addNode(Element.empty('br'));
-    return true;
-  }
-}
-
-/// Matches stuff that should just be passed through as straight text.
-class TextSyntax extends InlineSyntax {
-  TextSyntax(String pattern, {String? sub})
-      : substitute = sub,
-        super(pattern);
-
-  final String? substitute;
-
-  @override
-  bool onMatch(InlineParser parser, Match match) {
-    if (substitute == null) {
-      // Just use the original matched text.
-      parser.advanceBy(match[0]!.length);
-      return false;
-    }
-
-    // Insert the substitution.
-    parser.addNode(Text(substitute!));
-    return true;
-  }
-}
-
-/// Escape punctuation preceded by a backslash.
-class EscapeSyntax extends InlineSyntax {
-  EscapeSyntax() : super(r'''\\[!"#$%&'()*+,\-./:;<=>?@\[\\\]^_`{|}~]''');
-
-  @override
-  bool onMatch(InlineParser parser, Match match) {
-    // Insert the substitution.
-    parser.addNode(Text(match[0]![1]));
-    return true;
-  }
-}
-
-/// Leave inline HTML tags alone, from
-/// [CommonMark 0.28](http://spec.commonmark.org/0.28/#raw-html).
-///
-/// This is not actually a good definition (nor CommonMark's) of an HTML tag,
-/// but it is fast. It will leave text like `<a href='hi">` alone, which is
-/// incorrect.
-///
-/// TODO(srawlins): improve accuracy while ensuring performance, once
-/// Markdown benchmarking is more mature.
-class InlineHtmlSyntax extends TextSyntax {
-  InlineHtmlSyntax() : super(r'<[/!?]?[A-Za-z][A-Za-z0-9-]*(?:\s[^>]*)?>');
-}
-
-/// Matches autolinks like `<[email protected]>`.
-///
-/// See <http://spec.commonmark.org/0.28/#email-address>.
-class EmailAutolinkSyntax extends InlineSyntax {
-  EmailAutolinkSyntax() : super('<($_email)>');
-
-  static const _email =
-      r'''[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}'''
-      r'''[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*''';
-
-  @override
-  bool onMatch(InlineParser parser, Match match) {
-    final url = match[1]!;
-    final anchor = Element.text('a', escapeHtml(url));
-    anchor.attributes['href'] = Uri.encodeFull('mailto:$url');
-    parser.addNode(anchor);
-
-    return true;
-  }
-}
-
-/// Matches autolinks like `<http://foo.com>`.
-class AutolinkSyntax extends InlineSyntax {
-  AutolinkSyntax() : super(r'<(([a-zA-Z][a-zA-Z\-\+\.]+):(?://)?[^\s>]*)>');
-
-  @override
-  bool onMatch(InlineParser parser, Match match) {
-    final url = match[1]!;
-    final anchor = Element.text('a', escapeHtml(url));
-    anchor.attributes['href'] = Uri.encodeFull(url);
-    parser.addNode(anchor);
-
-    return true;
-  }
-}
-
-/// Matches autolinks like `http://foo.com`.
-class AutolinkExtensionSyntax extends InlineSyntax {
-  AutolinkExtensionSyntax() : super('$start(($scheme)($domain)($path))');
-
-  /// Broken up parts of the autolink regex for reusability and readability
-
-  // Autolinks can only come at the beginning of a line, after whitespace, or
-  // any of the delimiting characters *, _, ~, and (.
-  static const start = r'(?:^|[\s*_~(>])';
-  // An extended url autolink will be recognized when one of the schemes
-  // http://, https://, or ftp://, followed by a valid domain
-  static const scheme = r'(?:(?:https?|ftp):\/\/|www\.)';
-  // A valid domain consists of alphanumeric characters, underscores (_),
-  // hyphens (-) and periods (.). There must be at least one period, and no
-  // underscores may be present in the last two segments of the domain.
-  static const domainPart = r'\w\-';
-  static const domain = '[$domainPart][$domainPart.]+';
-  // A valid domain consists of alphanumeric characters, underscores (_),
-  // hyphens (-) and periods (.).
-  static const path = r'[^\s<]*';
-  // Trailing punctuation (specifically, ?, !, ., ,, :, *, _, and ~) will not
-  // be considered part of the autolink
-  static const truncatingPunctuationPositive = r'[?!.,:*_~]';
-
-  static final regExpTrailingPunc =
-      RegExp('$truncatingPunctuationPositive*' r'$');
-  static final regExpEndsWithColon = RegExp(r'\&[a-zA-Z0-9]+;$');
-  static final regExpWhiteSpace = RegExp(r'\s');
-
-  @override
-  bool tryMatch(InlineParser parser, [int? startMatchPos]) {
-    return super.tryMatch(parser, parser.pos > 0 ? parser.pos - 1 : 0);
-  }
-
-  @override
-  bool onMatch(InlineParser parser, Match match) {
-    var url = match[1]!;
-    var href = url;
-    var matchLength = url.length;
-
-    if (url[0] == '>' || url.startsWith(regExpWhiteSpace)) {
-      url = url.substring(1, url.length - 1);
-      href = href.substring(1, href.length - 1);
-      parser.pos++;
-      matchLength--;
-    }
-
-    // Prevent accidental standard autolink matches
-    if (url.endsWith('>') && parser.source[parser.pos - 1] == '<') {
-      return false;
-    }
-
-    // When an autolink ends in ), we scan the entire autolink for the total
-    // number of parentheses. If there is a greater number of closing
-    // parentheses than opening ones, we don’t consider the last character
-    // part of the autolink, in order to facilitate including an autolink
-    // inside a parenthesis:
-    // https://github.github.com/gfm/#example-600
-    if (url.endsWith(')')) {
-      final opening = _countChars(url, '(');
-      final closing = _countChars(url, ')');
-
-      if (closing > opening) {
-        url = url.substring(0, url.length - 1);
-        href = href.substring(0, href.length - 1);
-        matchLength--;
-      }
-    }
-
-    // Trailing punctuation (specifically, ?, !, ., ,, :, *, _, and ~) will
-    // not be considered part of the autolink, though they may be included
-    // in the interior of the link:
-    // https://github.github.com/gfm/#example-599
-    final trailingPunc = regExpTrailingPunc.firstMatch(url);
-    if (trailingPunc != null) {
-      url = url.substring(0, url.length - trailingPunc[0]!.length);
-      href = href.substring(0, href.length - trailingPunc[0]!.length);
-      matchLength -= trailingPunc[0]!.length;
-    }
-
-    // If an autolink ends in a semicolon (;), we check to see if it appears
-    // to resemble an
-    // [entity reference](https://github.github.com/gfm/#entity-references);
-    // if the preceding text is & followed by one or more alphanumeric
-    // characters. If so, it is excluded from the autolink:
-    // https://github.github.com/gfm/#example-602
-    if (url.endsWith(';')) {
-      final entityRef = regExpEndsWithColon.firstMatch(url);
-      if (entityRef != null) {
-        // Strip out HTML entity reference
-        url = url.substring(0, url.length - entityRef[0]!.length);
-        href = href.substring(0, href.length - entityRef[0]!.length);
-        matchLength -= entityRef[0]!.length;
-      }
-    }
-
-    // The scheme http will be inserted automatically
-    if (!href.startsWith('http://') &&
-        !href.startsWith('https://') &&
-        !href.startsWith('ftp://')) {
-      href = 'http://$href';
-    }
-
-    final anchor = Element.text('a', escapeHtml(url));
-    anchor.attributes['href'] = Uri.encodeFull(href);
-    parser
-      ..addNode(anchor)
-      ..consume(matchLength);
-    return false;
-  }
-
-  int _countChars(String input, String char) {
-    var count = 0;
-
-    for (var i = 0; i < input.length; i++) {
-      if (input[i] == char) {
-        count++;
-      }
-    }
-
-    return count;
-  }
-}
-
-class DelimiterRun {
-  DelimiterRun._(
-      {this.char,
-      this.length,
-      this.isLeftFlanking,
-      this.isRightFlanking,
-      this.isPrecededByPunctuation,
-      this.isFollowedByPunctuation});
-
-  static const String punctuation = r'''!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~''';
-  // TODO(srawlins): Unicode whitespace
-  static const String whitespace = ' \t\r\n';
-
-  final int? char;
-  final int? length;
-  final bool? isLeftFlanking;
-  final bool? isRightFlanking;
-  final bool? isPrecededByPunctuation;
-  final bool? isFollowedByPunctuation;
-
-  // ignore: prefer_constructors_over_static_methods
-  static DelimiterRun? tryParse(InlineParser parser, int runStart, int runEnd) {
-    bool leftFlanking,
-        rightFlanking,
-        precededByPunctuation,
-        followedByPunctuation;
-    String preceding, following;
-    if (runStart == 0) {
-      rightFlanking = false;
-      preceding = '\n';
-    } else {
-      preceding = parser.source.substring(runStart - 1, runStart);
-    }
-    precededByPunctuation = punctuation.contains(preceding);
-
-    if (runEnd == parser.source.length - 1) {
-      leftFlanking = false;
-      following = '\n';
-    } else {
-      following = parser.source.substring(runEnd + 1, runEnd + 2);
-    }
-    followedByPunctuation = punctuation.contains(following);
-
-    // http://spec.commonmark.org/0.28/#left-flanking-delimiter-run
-    if (whitespace.contains(following)) {
-      leftFlanking = false;
-    } else {
-      leftFlanking = !followedByPunctuation ||
-          whitespace.contains(preceding) ||
-          precededByPunctuation;
-    }
-
-    // http://spec.commonmark.org/0.28/#right-flanking-delimiter-run
-    if (whitespace.contains(preceding)) {
-      rightFlanking = false;
-    } else {
-      rightFlanking = !precededByPunctuation ||
-          whitespace.contains(following) ||
-          followedByPunctuation;
-    }
-
-    if (!leftFlanking && !rightFlanking) {
-      // Could not parse a delimiter run.
-      return null;
-    }
-
-    return DelimiterRun._(
-        char: parser.charAt(runStart),
-        length: runEnd - runStart + 1,
-        isLeftFlanking: leftFlanking,
-        isRightFlanking: rightFlanking,
-        isPrecededByPunctuation: precededByPunctuation,
-        isFollowedByPunctuation: followedByPunctuation);
-  }
-
-  @override
-  String toString() =>
-      '<char: $char, length: $length, isLeftFlanking: $isLeftFlanking, '
-      'isRightFlanking: $isRightFlanking>';
-
-  // Whether a delimiter in this run can open emphasis or strong emphasis.
-  bool get canOpen =>
-      isLeftFlanking! &&
-      (char == $asterisk || !isRightFlanking! || isPrecededByPunctuation!);
-
-  // Whether a delimiter in this run can close emphasis or strong emphasis.
-  bool get canClose =>
-      isRightFlanking! &&
-      (char == $asterisk || !isLeftFlanking! || isFollowedByPunctuation!);
-}
-
-/// Matches syntax that has a pair of tags and becomes an element, like `*` for
-/// `<em>`. Allows nested tags.
-class TagSyntax extends InlineSyntax {
-  TagSyntax(String pattern, {String? end, this.requiresDelimiterRun = false})
-      : endPattern = RegExp((end != null) ? end : pattern, multiLine: true),
-        super(pattern);
-
-  final RegExp endPattern;
-
-  /// Whether this is parsed according to the same nesting rules as [emphasis
-  /// delimiters][].
-  ///
-  /// [emphasis delimiters]: http://spec.commonmark.org/0.28/#can-open-emphasis
-  final bool requiresDelimiterRun;
-
-  @override
-  bool onMatch(InlineParser parser, Match match) {
-    final runLength = match.group(0)!.length;
-    final matchStart = parser.pos;
-    final matchEnd = parser.pos + runLength - 1;
-    if (!requiresDelimiterRun) {
-      parser.openTag(TagState(parser.pos, matchEnd + 1, this, null));
-      return true;
-    }
-
-    final delimiterRun = DelimiterRun.tryParse(parser, matchStart, matchEnd);
-    if (delimiterRun != null && delimiterRun.canOpen) {
-      parser.openTag(TagState(parser.pos, matchEnd + 1, this, delimiterRun));
-      return true;
-    } else {
-      parser.advanceBy(runLength);
-      return false;
-    }
-  }
-
-  bool onMatchEnd(InlineParser parser, Match match, TagState state) {
-    final runLength = match.group(0)!.length;
-    final matchStart = parser.pos;
-    final matchEnd = parser.pos + runLength - 1;
-    final openingRunLength = state.endPos - state.startPos;
-    final delimiterRun = DelimiterRun.tryParse(parser, matchStart, matchEnd);
-
-    if (openingRunLength == 1 && runLength == 1) {
-      parser.addNode(Element('em', state.children));
-    } else if (openingRunLength == 1 && runLength > 1) {
-      parser
-        ..addNode(Element('em', state.children))
-        ..pos = parser.pos - (runLength - 1)
-        ..start = parser.pos;
-    } else if (openingRunLength > 1 && runLength == 1) {
-      parser
-        ..openTag(
-            TagState(state.startPos, state.endPos - 1, this, delimiterRun))
-        ..addNode(Element('em', state.children));
-    } else if (openingRunLength == 2 && runLength == 2) {
-      parser.addNode(Element('strong', state.children));
-    } else if (openingRunLength == 2 && runLength > 2) {
-      parser
-        ..addNode(Element('strong', state.children))
-        ..pos = parser.pos - (runLength - 2)
-        ..start = parser.pos;
-    } else if (openingRunLength > 2 && runLength == 2) {
-      parser
-        ..openTag(
-            TagState(state.startPos, state.endPos - 2, this, delimiterRun))
-        ..addNode(Element('strong', state.children));
-    } else if (openingRunLength > 2 && runLength > 2) {
-      parser
-        ..openTag(
-            TagState(state.startPos, state.endPos - 2, this, delimiterRun))
-        ..addNode(Element('strong', state.children))
-        ..pos = parser.pos - (runLength - 2)
-        ..start = parser.pos;
-    }
-
-    return true;
-  }
-}
-
-/// Matches strikethrough syntax according to the GFM spec.
-class StrikethroughSyntax extends TagSyntax {
-  StrikethroughSyntax() : super('~+', requiresDelimiterRun: true);
-
-  @override
-  bool onMatchEnd(InlineParser parser, Match match, TagState state) {
-    final runLength = match.group(0)!.length;
-    final matchStart = parser.pos;
-    final matchEnd = parser.pos + runLength - 1;
-    final delimiterRun = DelimiterRun.tryParse(parser, matchStart, matchEnd)!;
-    if (!delimiterRun.isRightFlanking!) {
-      return false;
-    }
-
-    parser.addNode(Element('del', state.children));
-    return true;
-  }
-}
-
-/// Matches links like `[blah][label]` and `[blah](url)`.
-class LinkSyntax extends TagSyntax {
-  LinkSyntax({Resolver? linkResolver, String pattern = r'\['})
-      : linkResolver = (linkResolver ?? (_, [__]) => null),
-        super(pattern, end: r'\]');
-
-  static final _entirelyWhitespacePattern = RegExp(r'^\s*$');
-
-  final Resolver linkResolver;
-
-  // The pending [TagState]s, all together, are "active" or "inactive" based on
-  // whether a link element has just been parsed.
-  //
-  // Links cannot be nested, so we must "deactivate" any pending ones. For
-  // example, take the following text:
-  //
-  //     Text [link and [more](links)](links).
-  //
-  // Once we have parsed `Text [`, there is one (pending) link in the state
-  // stack.  It is, by default, active. Once we parse the next possible link,
-  // `[more](links)`, as a real link, we must deactivate the pending links (just
-  // the one, in this case).
-  var _pendingStatesAreActive = true;
-
-  @override
-  bool onMatch(InlineParser parser, Match match) {
-    final matched = super.onMatch(parser, match);
-    if (!matched) {
-      return false;
-    }
-
-    _pendingStatesAreActive = true;
-
-    return true;
-  }
-
-  @override
-  bool onMatchEnd(InlineParser parser, Match match, TagState state) {
-    if (!_pendingStatesAreActive) {
-      return false;
-    }
-
-    final text = parser.source.substring(state.endPos, parser.pos);
-    // The current character is the `]` that closed the link text. Examine the
-    // next character, to determine what type of link we might have (a '('
-    // means a possible inline link; otherwise a possible reference link).
-    if (parser.pos + 1 >= parser.source.length) {
-      // In this case, the Markdown document may have ended with a shortcut
-      // reference link.
-
-      return _tryAddReferenceLink(parser, state, text);
-    }
-    // Peek at the next character; don't advance, so as to avoid later stepping
-    // backward.
-    final char = parser.charAt(parser.pos + 1);
-
-    if (char == $lparen) {
-      // Maybe an inline link, like `[text](destination)`.
-      parser.advanceBy(1);
-      final leftParenIndex = parser.pos;
-      final inlineLink = _parseInlineLink(parser);
-      if (inlineLink != null) {
-        return _tryAddInlineLink(parser, state, inlineLink);
-      }
-
-      // Reset the parser position.
-      parser
-        ..pos = leftParenIndex
-
-        // At this point, we've matched `[...](`, but that `(` did not pan out
-        // to be an inline link. We must now check if `[...]` is simply a
-        // shortcut reference link.
-        ..advanceBy(-1);
-      return _tryAddReferenceLink(parser, state, text);
-    }
-
-    if (char == $lbracket) {
-      parser.advanceBy(1);
-      // At this point, we've matched `[...][`. Maybe a *full* reference link,
-      // like `[foo][bar]` or a *collapsed* reference link, like `[foo][]`.
-      if (parser.pos + 1 < parser.source.length &&
-          parser.charAt(parser.pos + 1) == $rbracket) {
-        // That opening `[` is not actually part of the link. Maybe a
-        // *shortcut* reference link (followed by a `[`).
-        parser.advanceBy(1);
-        return _tryAddReferenceLink(parser, state, text);
-      }
-      final label = _parseReferenceLinkLabel(parser);
-      if (label != null) {
-        return _tryAddReferenceLink(parser, state, label);
-      }
-      return false;
-    }
-
-    // The link text (inside `[...]`) was not followed with a opening `(` nor
-    // an opening `[`. Perhaps just a simple shortcut reference link (`[...]`).
-
-    return _tryAddReferenceLink(parser, state, text);
-  }
-
-  /// Resolve a possible reference link.
-  ///
-  /// Uses [linkReferences], [linkResolver], and [_createNode] to try to
-  /// resolve [label] and [state] into a [Node]. If [label] is defined in
-  /// [linkReferences] or can be resolved by [linkResolver], returns a [Node]
-  /// that links to the resolved URL.
-  ///
-  /// Otherwise, returns `null`.
-  ///
-  /// [label] does not need to be normalized.
-  Node? _resolveReferenceLink(
-      String label, TagState state, Map<String, LinkReference> linkReferences) {
-    final normalizedLabel = label.toLowerCase();
-    final linkReference = linkReferences[normalizedLabel];
-    if (linkReference != null) {
-      return _createNode(state, linkReference.destination, linkReference.title);
-    } else {
-      // This link has no reference definition. But we allow users of the
-      // library to specify a custom resolver function ([linkResolver]) that
-      // may choose to handle this. Otherwise, it's just treated as plain
-      // text.
-
-      // Normally, label text does not get parsed as inline Markdown. However,
-      // for the benefit of the link resolver, we need to at least escape
-      // brackets, so that, e.g. a link resolver can receive `[\[\]]` as `[]`.
-      return linkResolver(label
-          .replaceAll(r'\\', r'\')
-          .replaceAll(r'\[', '[')
-          .replaceAll(r'\]', ']'));
-    }
-  }
-
-  /// Create the node represented by a Markdown link.
-  Node _createNode(TagState state, String destination, String? title) {
-    final element = Element('a', state.children);
-    element.attributes['href'] = escapeAttribute(destination);
-    if (title != null && title.isNotEmpty) {
-      element.attributes['title'] = escapeAttribute(title);
-    }
-    return element;
-  }
-
-  // Add a reference link node to [parser]'s AST.
-  //
-  // Returns whether the link was added successfully.
-  bool _tryAddReferenceLink(InlineParser parser, TagState state, String label) {
-    final element =
-        _resolveReferenceLink(label, state, parser.document.linkReferences);
-    if (element == null) {
-      return false;
-    }
-    parser
-      ..addNode(element)
-      ..start = parser.pos;
-    _pendingStatesAreActive = false;
-    return true;
-  }
-
-  // Add an inline link node to [parser]'s AST.
-  //
-  // Returns whether the link was added successfully.
-  bool _tryAddInlineLink(InlineParser parser, TagState state, InlineLink link) {
-    final element = _createNode(state, link.destination, link.title);
-    parser
-      ..addNode(element)
-      ..start = parser.pos;
-    _pendingStatesAreActive = false;
-    return true;
-  }
-
-  /// Parse a reference link label at the current position.
-  ///
-  /// Specifically, [parser.pos] is expected to be pointing at the `[` which
-  /// opens the link label.
-  ///
-  /// Returns the label if it could be parsed, or `null` if not.
-  String? _parseReferenceLinkLabel(InlineParser parser) {
-    // Walk past the opening `[`.
-    parser.advanceBy(1);
-    if (parser.isDone) {
-      return null;
-    }
-
-    final buffer = StringBuffer();
-    while (true) {
-      final char = parser.charAt(parser.pos);
-      if (char == $backslash) {
-        parser.advanceBy(1);
-        final next = parser.charAt(parser.pos);
-        if (next != $backslash && next != $rbracket) {
-          buffer.writeCharCode(char);
-        }
-        buffer.writeCharCode(next);
-      } else if (char == $rbracket) {
-        break;
-      } else {
-        buffer.writeCharCode(char);
-      }
-      parser.advanceBy(1);
-      if (parser.isDone) {
-        return null;
-      }
-      // TODO(srawlins): only check 999 characters, for performance reasons?
-    }
-
-    final label = buffer.toString();
-
-    // A link label must contain at least one non-whitespace character.
-    if (_entirelyWhitespacePattern.hasMatch(label)) {
-      return null;
-    }
-
-    return label;
-  }
-
-  /// Parse an inline [InlineLink] at the current position.
-  ///
-  /// At this point, we have parsed a link's (or image's) opening `[`, and then
-  /// a matching closing `]`, and [parser.pos] is pointing at an opening `(`.
-  /// This method will then attempt to parse a link destination wrapped in `<>`,
-  /// such as `(<http://url>)`, or a bare link destination, such as
-  /// `(http://url)`, or a link destination with a title, such as
-  /// `(http://url "title")`.
-  ///
-  /// Returns the [InlineLink] if one was parsed, or `null` if not.
-  InlineLink? _parseInlineLink(InlineParser parser) {
-    // Start walking to the character just after the opening `(`.
-    parser.advanceBy(1);
-
-    _moveThroughWhitespace(parser);
-    if (parser.isDone) {
-      return null; // EOF. Not a link.
-    }
-
-    if (parser.charAt(parser.pos) == $lt) {
-      // Maybe a `<...>`-enclosed link destination.
-      return _parseInlineBracketedLink(parser);
-    } else {
-      return _parseInlineBareDestinationLink(parser);
-    }
-  }
-
-  /// Parse an inline link with a bracketed destination (a destination wrapped
-  /// in `<...>`). The current position of the parser must be the first
-  /// character of the destination.
-  InlineLink? _parseInlineBracketedLink(InlineParser parser) {
-    parser.advanceBy(1);
-
-    final buffer = StringBuffer();
-    while (true) {
-      final char = parser.charAt(parser.pos);
-      if (char == $backslash) {
-        parser.advanceBy(1);
-        final next = parser.charAt(parser.pos);
-        if (char == $space || char == $lf || char == $cr || char == $ff) {
-          // Not a link (no whitespace allowed within `<...>`).
-          return null;
-        }
-        // TODO: Follow the backslash spec better here.
-        // http://spec.commonmark.org/0.28/#backslash-escapes
-        if (next != $backslash && next != $gt) {
-          buffer.writeCharCode(char);
-        }
-        buffer.writeCharCode(next);
-      } else if (char == $space || char == $lf || char == $cr || char == $ff) {
-        // Not a link (no whitespace allowed within `<...>`).
-        return null;
-      } else if (char == $gt) {
-        break;
-      } else {
-        buffer.writeCharCode(char);
-      }
-      parser.advanceBy(1);
-      if (parser.isDone) {
-        return null;
-      }
-    }
-    final destination = buffer.toString();
-
-    parser.advanceBy(1);
-    final char = parser.charAt(parser.pos);
-    if (char == $space || char == $lf || char == $cr || char == $ff) {
-      final title = _parseTitle(parser);
-      if (title == null && parser.charAt(parser.pos) != $rparen) {
-        // This looked like an inline link, until we found this $space
-        // followed by mystery characters; no longer a link.
-        return null;
-      }
-      return InlineLink(destination, title: title);
-    } else if (char == $rparen) {
-      return InlineLink(destination);
-    } else {
-      // We parsed something like `[foo](<url>X`. Not a link.
-      return null;
-    }
-  }
-
-  /// Parse an inline link with a "bare" destination (a destination _not_
-  /// wrapped in `<...>`). The current position of the parser must be the first
-  /// character of the destination.
-  InlineLink? _parseInlineBareDestinationLink(InlineParser parser) {
-    // According to
-    // [CommonMark](http://spec.commonmark.org/0.28/#link-destination):
-    //
-    // > A link destination consists of [...] a nonempty sequence of
-    // > characters [...], and includes parentheses only if (a) they are
-    // > backslash-escaped or (b) they are part of a balanced pair of
-    // > unescaped parentheses.
-    //
-    // We need to count the open parens. We start with 1 for the paren that
-    // opened the destination.
-    var parenCount = 1;
-    final buffer = StringBuffer();
-
-    while (true) {
-      final char = parser.charAt(parser.pos);
-      switch (char) {
-        case $backslash:
-          parser.advanceBy(1);
-          if (parser.isDone) {
-            return null; // EOF. Not a link.
-          }
-
-          final next = parser.charAt(parser.pos);
-          // Parentheses may be escaped.
-          //
-          // http://spec.commonmark.org/0.28/#example-467
-          if (next != $backslash && next != $lparen && next != $rparen) {
-            buffer.writeCharCode(char);
-          }
-          buffer.writeCharCode(next);
-          break;
-
-        case $space:
-        case $lf:
-        case $cr:
-        case $ff:
-          final destination = buffer.toString();
-          final title = _parseTitle(parser);
-          if (title == null && parser.charAt(parser.pos) != $rparen) {
-            // This looked like an inline link, until we found this $space
-            // followed by mystery characters; no longer a link.
-            return null;
-          }
-          // [_parseTitle] made sure the title was followed by a closing `)`
-          // (but it's up to the code here to examine the balance of
-          // parentheses).
-          parenCount--;
-          if (parenCount == 0) {
-            return InlineLink(destination, title: title);
-          }
-          break;
-
-        case $lparen:
-          parenCount++;
-          buffer.writeCharCode(char);
-          break;
-
-        case $rparen:
-          parenCount--;
-          // ignore: invariant_booleans
-          if (parenCount == 0) {
-            final destination = buffer.toString();
-            return InlineLink(destination);
-          }
-          buffer.writeCharCode(char);
-          break;
-
-        default:
-          buffer.writeCharCode(char);
-      }
-      parser.advanceBy(1);
-      if (parser.isDone) {
-        return null; // EOF. Not a link.
-      }
-    }
-  }
-
-  // Walk the parser forward through any whitespace.
-  void _moveThroughWhitespace(InlineParser parser) {
-    while (true) {
-      final char = parser.charAt(parser.pos);
-      if (char != $space &&
-          char != $tab &&
-          char != $lf &&
-          char != $vt &&
-          char != $cr &&
-          char != $ff) {
-        return;
-      }
-      parser.advanceBy(1);
-      if (parser.isDone) {
-        return;
-      }
-    }
-  }
-
-  // Parse a link title in [parser] at it's current position. The parser's
-  // current position should be a whitespace character that followed a link
-  // destination.
-  String? _parseTitle(InlineParser parser) {
-    _moveThroughWhitespace(parser);
-    if (parser.isDone) {
-      return null;
-    }
-
-    // The whitespace should be followed by a title delimiter.
-    final delimiter = parser.charAt(parser.pos);
-    if (delimiter != $apostrophe &&
-        delimiter != $quote &&
-        delimiter != $lparen) {
-      return null;
-    }
-
-    final closeDelimiter = delimiter == $lparen ? $rparen : delimiter;
-    parser.advanceBy(1);
-
-    // Now we look for an un-escaped closing delimiter.
-    final buffer = StringBuffer();
-    while (true) {
-      final char = parser.charAt(parser.pos);
-      if (char == $backslash) {
-        parser.advanceBy(1);
-        final next = parser.charAt(parser.pos);
-        if (next != $backslash && next != closeDelimiter) {
-          buffer.writeCharCode(char);
-        }
-        buffer.writeCharCode(next);
-      } else if (char == closeDelimiter) {
-        break;
-      } else {
-        buffer.writeCharCode(char);
-      }
-      parser.advanceBy(1);
-      if (parser.isDone) {
-        return null;
-      }
-    }
-    final title = buffer.toString();
-
-    // Advance past the closing delimiter.
-    parser.advanceBy(1);
-    if (parser.isDone) {
-      return null;
-    }
-    _moveThroughWhitespace(parser);
-    if (parser.isDone) {
-      return null;
-    }
-    if (parser.charAt(parser.pos) != $rparen) {
-      return null;
-    }
-    return title;
-  }
-}
-
-/// Matches images like `![alternate text](url "optional title")` and
-/// `![alternate text][label]`.
-class ImageSyntax extends LinkSyntax {
-  ImageSyntax({Resolver? linkResolver})
-      : super(linkResolver: linkResolver, pattern: r'!\[');
-
-  @override
-  Node _createNode(TagState state, String destination, String? title) {
-    final element = Element.empty('img');
-    element.attributes['src'] = escapeHtml(destination);
-    element.attributes['alt'] = state.textContent;
-    if (title != null && title.isNotEmpty) {
-      element.attributes['title'] = escapeAttribute(title);
-    }
-    return element;
-  }
-
-  // Add an image node to [parser]'s AST.
-  //
-  // If [label] is present, the potential image is treated as a reference image.
-  // Otherwise, it is treated as an inline image.
-  //
-  // Returns whether the image was added successfully.
-  @override
-  bool _tryAddReferenceLink(InlineParser parser, TagState state, String label) {
-    final element =
-        _resolveReferenceLink(label, state, parser.document.linkReferences);
-    if (element == null) {
-      return false;
-    }
-    parser
-      ..addNode(element)
-      ..start = parser.pos;
-    return true;
-  }
-}
-
-/// Matches backtick-enclosed inline code blocks.
-class CodeSyntax extends InlineSyntax {
-  CodeSyntax() : super(_pattern);
-
-  // This pattern matches:
-  //
-  // * a string of backticks (not followed by any more), followed by
-  // * a non-greedy string of anything, including newlines, ending with anything
-  //   except a backtick, followed by
-  // * a string of backticks the same length as the first, not followed by any
-  //   more.
-  //
-  // This conforms to the delimiters of inline code, both in Markdown.pl, and
-  // CommonMark.
-  static const String _pattern = r'(`+(?!`))((?:.|\n)*?[^`])\1(?!`)';
-
-  @override
-  bool tryMatch(InlineParser parser, [int? startMatchPos]) {
-    if (parser.pos > 0 && parser.charAt(parser.pos - 1) == $backquote) {
-      // Not really a match! We can't just sneak past one backtick to try the
-      // next character. An example of this situation would be:
-      //
-      //     before ``` and `` after.
-      //             ^--parser.pos
-      return false;
-    }
-
-    final match = pattern.matchAsPrefix(parser.source, parser.pos);
-    if (match == null) {
-      return false;
-    }
-    parser.writeText();
-    if (onMatch(parser, match)) {
-      parser.consume(match[0]!.length);
-    }
-    return true;
-  }
-
-  @override
-  bool onMatch(InlineParser parser, Match match) {
-    parser.addNode(Element.text('code', escapeHtml(match[2]!.trim())));
-    return true;
-  }
-}
-
-/// Matches GitHub Markdown emoji syntax like `:smile:`.
-///
-/// There is no formal specification of GitHub's support for this colon-based
-/// emoji support, so this syntax is based on the results of Markdown-enabled
-/// text fields at github.com.
-class EmojiSyntax extends InlineSyntax {
-  // Emoji "aliases" are mostly limited to lower-case letters, numbers, and
-  // underscores, but GitHub also supports `:+1:` and `:-1:`.
-  EmojiSyntax() : super(':([a-z0-9_+-]+):');
-
-  @override
-  bool onMatch(InlineParser parser, Match match) {
-    final alias = match[1];
-    final emoji = emojis[alias!];
-    if (emoji == null) {
-      parser.advanceBy(1);
-      return false;
-    }
-    parser.addNode(Text(emoji));
-
-    return true;
-  }
-}
-
-/// Keeps track of a currently open tag while it is being parsed.
-///
-/// The parser maintains a stack of these so it can handle nested tags.
-class TagState {
-  TagState(this.startPos, this.endPos, this.syntax, this.openingDelimiterRun)
-      : children = <Node>[];
-
-  /// The point in the original source where this tag started.
-  final int startPos;
-
-  /// The point in the original source where open tag ended.
-  final int endPos;
-
-  /// The syntax that created this node.
-  final TagSyntax? syntax;
-
-  /// The children of this node. Will be `null` for text nodes.
-  final List<Node> children;
-
-  final DelimiterRun? openingDelimiterRun;
-
-  /// Attempts to close this tag by matching the current text against its end
-  /// pattern.
-  bool tryMatch(InlineParser parser) {
-    final endMatch =
-        syntax!.endPattern.matchAsPrefix(parser.source, parser.pos);
-    if (endMatch == null) {
-      return false;
-    }
-
-    if (!syntax!.requiresDelimiterRun) {
-      // Close the tag.
-      close(parser, endMatch);
-      return true;
-    }
-
-    // TODO: Move this logic into TagSyntax.
-    final runLength = endMatch.group(0)!.length;
-    final openingRunLength = endPos - startPos;
-    final closingMatchStart = parser.pos;
-    final closingMatchEnd = parser.pos + runLength - 1;
-    final closingDelimiterRun =
-        DelimiterRun.tryParse(parser, closingMatchStart, closingMatchEnd);
-    if (closingDelimiterRun != null && closingDelimiterRun.canClose) {
-      // Emphasis rules #9 and #10:
-      final oneRunOpensAndCloses =
-          (openingDelimiterRun!.canOpen && openingDelimiterRun!.canClose) ||
-              (closingDelimiterRun.canOpen && closingDelimiterRun.canClose);
-      if (oneRunOpensAndCloses &&
-          (openingRunLength + closingDelimiterRun.length!) % 3 == 0) {
-        return false;
-      }
-      // Close the tag.
-      close(parser, endMatch);
-      return true;
-    } else {
-      return false;
-    }
-  }
-
-  /// Pops this tag off the stack, completes it, and adds it to the output.
-  ///
-  /// Will discard any unmatched tags that happen to be above it on the stack.
-  /// If this is the last node in the stack, returns its children.
-  List<Node>? close(InlineParser parser, Match? endMatch) {
-    // If there are unclosed tags on top of this one when it's closed, that
-    // means they are mismatched. Mismatched tags are treated as plain text in
-    // markdown. So for each tag above this one, we write its start tag as text
-    // and then adds its children to this one's children.
-    final index = parser._stack.indexOf(this);
-
-    // Remove the unmatched children.
-    final unmatchedTags = parser._stack.sublist(index + 1);
-    parser._stack.removeRange(index + 1, parser._stack.length);
-
-    // Flatten them out onto this tag.
-    for (final unmatched in unmatchedTags) {
-      // Write the start tag as text.
-      parser.writeTextRange(unmatched.startPos, unmatched.endPos);
-
-      // Bequeath its children unto this tag.
-      children.addAll(unmatched.children);
-    }
-
-    // Pop this off the stack.
-    parser.writeText();
-    parser._stack.removeLast();
-
-    // If the stack is empty now, this is the special "results" node.
-    if (parser._stack.isEmpty) {
-      return children;
-    }
-    final endMatchIndex = parser.pos;
-
-    // We are still parsing, so add this to its parent's children.
-    if (syntax!.onMatchEnd(parser, endMatch!, this)) {
-      parser.consume(endMatch[0]!.length);
-    } else {
-      // Didn't close correctly so revert to text.
-      parser
-        ..writeTextRange(startPos, endPos)
-        .._stack.last.children.addAll(children)
-        ..pos = endMatchIndex
-        ..advanceBy(endMatch[0]!.length);
-    }
-
-    return null;
-  }
-
-  String get textContent => children.map((child) => child.textContent).join();
-}
-
-class InlineLink {
-  InlineLink(this.destination, {this.title});
-
-  final String destination;
-  final String? title;
-}

+ 0 - 71
frontend/app_flowy/lib/workspace/application/markdown/src/util.dart

@@ -1,71 +0,0 @@
-import 'dart:convert';
-
-import 'package:charcode/charcode.dart';
-
-String escapeHtml(String html) =>
-    const HtmlEscape(HtmlEscapeMode.element).convert(html);
-
-// Escape the contents of [value], so that it may be used as an HTML attribute.
-
-// Based on http://spec.commonmark.org/0.28/#backslash-escapes.
-String escapeAttribute(String value) {
-  final result = StringBuffer();
-  int ch;
-  for (var i = 0; i < value.codeUnits.length; i++) {
-    ch = value.codeUnitAt(i);
-    if (ch == $backslash) {
-      i++;
-      if (i == value.codeUnits.length) {
-        result.writeCharCode(ch);
-        break;
-      }
-      ch = value.codeUnitAt(i);
-      switch (ch) {
-        case $quote:
-          result.write('&quot;');
-          break;
-        case $exclamation:
-        case $hash:
-        case $dollar:
-        case $percent:
-        case $ampersand:
-        case $apostrophe:
-        case $lparen:
-        case $rparen:
-        case $asterisk:
-        case $plus:
-        case $comma:
-        case $dash:
-        case $dot:
-        case $slash:
-        case $colon:
-        case $semicolon:
-        case $lt:
-        case $equal:
-        case $gt:
-        case $question:
-        case $at:
-        case $lbracket:
-        case $backslash:
-        case $rbracket:
-        case $caret:
-        case $underscore:
-        case $backquote:
-        case $lbrace:
-        case $bar:
-        case $rbrace:
-        case $tilde:
-          result.writeCharCode(ch);
-          break;
-        default:
-          result.write('%5C');
-          result.writeCharCode(ch);
-      }
-    } else if (ch == $quote) {
-      result.write('%22');
-    } else {
-      result.writeCharCode(ch);
-    }
-  }
-  return result.toString();
-}

+ 0 - 2
frontend/app_flowy/lib/workspace/application/markdown/src/version.dart

@@ -1,2 +0,0 @@
-// Generated code. Do not modify.
-const packageVersion = '0.0.2';

+ 42 - 83
frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_button.dart

@@ -1,21 +1,17 @@
-import 'package:app_flowy/plugins/doc/presentation/toolbar/toolbar_icon_button.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
-import 'package:flutter_quill/flutter_quill.dart';
 import 'package:app_flowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart';
 import 'package:app_flowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart';
 
 
 class FlowyEmojiStyleButton extends StatefulWidget {
 class FlowyEmojiStyleButton extends StatefulWidget {
   // final Attribute attribute;
   // final Attribute attribute;
   final String normalIcon;
   final String normalIcon;
-  final double iconSize;
-  final QuillController controller;
+  // TODO: enable insert emoji in appflowy_editor
+  // final QuillController controller;
   final String tooltipText;
   final String tooltipText;
 
 
   const FlowyEmojiStyleButton({
   const FlowyEmojiStyleButton({
     // required this.attribute,
     // required this.attribute,
     required this.normalIcon,
     required this.normalIcon,
-    required this.controller,
     required this.tooltipText,
     required this.tooltipText,
-    this.iconSize = defaultIconSize,
     Key? key,
     Key? key,
   }) : super(key: key);
   }) : super(key: key);
 
 
@@ -24,7 +20,6 @@ class FlowyEmojiStyleButton extends StatefulWidget {
 }
 }
 
 
 class EmojiStyleButtonState extends State<FlowyEmojiStyleButton> {
 class EmojiStyleButtonState extends State<FlowyEmojiStyleButton> {
-  bool _isToggled = false;
   // Style get _selectionStyle => widget.controller.getSelectionStyle();
   // Style get _selectionStyle => widget.controller.getSelectionStyle();
   final GlobalKey emojiButtonKey = GlobalKey();
   final GlobalKey emojiButtonKey = GlobalKey();
   OverlayEntry? _entry;
   OverlayEntry? _entry;
@@ -42,14 +37,15 @@ class EmojiStyleButtonState extends State<FlowyEmojiStyleButton> {
     // debugPrint(MediaQuery.of(context).size.width.toString());
     // debugPrint(MediaQuery.of(context).size.width.toString());
     // debugPrint(MediaQuery.of(context).size.height.toString());
     // debugPrint(MediaQuery.of(context).size.height.toString());
 
 
-    return ToolbarIconButton(
-      key: emojiButtonKey,
-      onPressed: _toggleAttribute,
-      width: widget.iconSize * kIconButtonFactor,
-      isToggled: _isToggled,
-      iconName: widget.normalIcon,
-      tooltipText: widget.tooltipText,
-    );
+    // return ToolbarIconButton(
+    //   key: emojiButtonKey,
+    //   onPressed: _toggleAttribute,
+    //   width: widget.iconSize * kIconButtonFactor,
+    //   isToggled: _isToggled,
+    //   iconName: widget.normalIcon,
+    //   tooltipText: widget.tooltipText,
+    // );
+    return Container();
   }
   }
 
 
   @override
   @override
@@ -58,71 +54,34 @@ class EmojiStyleButtonState extends State<FlowyEmojiStyleButton> {
     super.dispose();
     super.dispose();
   }
   }
 
 
-  // @override
-  // void didUpdateWidget(covariant FlowyEmojiStyleButton oldWidget) {
-  //   super.didUpdateWidget(oldWidget);
-  //   if (oldWidget.controller != widget.controller) {
-  //     oldWidget.controller.removeListener(_didChangeEditingValue);
-  //     widget.controller.addListener(_didChangeEditingValue);
-  //     _isToggled = _getIsToggled(_selectionStyle.attributes);
+  // void _toggleAttribute() {
+  //   if (_entry?.mounted ?? false) {
+  //     _entry?.remove();
+  //     _entry = null;
+  //     setState(() => _isToggled = false);
+  //   } else {
+  //     RenderBox box =
+  //         emojiButtonKey.currentContext?.findRenderObject() as RenderBox;
+  //     Offset position = box.localToGlobal(Offset.zero);
+
+  //     // final window = await getWindowInfo();
+
+  //     _entry = OverlayEntry(
+  //       builder: (BuildContext context) => BuildEmojiPickerView(
+  //         controller: widget.controller,
+  //         offset: position,
+  //       ),
+  //     );
+
+  //     Overlay.of(context)!.insert(_entry!);
+  //     setState(() => _isToggled = true);
   //   }
   //   }
-  // }
-
-  // @override
-  // void dispose() {
-  //   widget.controller.removeListener(_didChangeEditingValue);
-  //   super.dispose();
-  // }
 
 
-  // void _didChangeEditingValue() {
-  //   setState(() => _isToggled = _getIsToggled(_selectionStyle.attributes));
   // }
   // }
-
-  // bool _getIsToggled(Map<String, Attribute> attrs) {
-  //   return _entry.mounted;
-  // }
-
-  void _toggleAttribute() {
-    if (_entry?.mounted ?? false) {
-      _entry?.remove();
-      _entry = null;
-      setState(() => _isToggled = false);
-    } else {
-      RenderBox box =
-          emojiButtonKey.currentContext?.findRenderObject() as RenderBox;
-      Offset position = box.localToGlobal(Offset.zero);
-
-      // final window = await getWindowInfo();
-
-      _entry = OverlayEntry(
-        builder: (BuildContext context) => BuildEmojiPickerView(
-          controller: widget.controller,
-          offset: position,
-        ),
-      );
-
-      Overlay.of(context)!.insert(_entry!);
-      setState(() => _isToggled = true);
-    }
-
-    //TODO @gaganyadav80: INFO: throws error when using TextField with FlowyOverlay.
-
-    // FlowyOverlay.of(context).insertWithRect(
-    //   widget: BuildEmojiPickerView(controller: widget.controller),
-    //   identifier: 'overlay_emoji_picker',
-    //   anchorPosition: Offset(position.dx + 40, position.dy - 10),
-    //   anchorSize: window.frame.size,
-    //   anchorDirection: AnchorDirection.topLeft,
-    //   style: FlowyOverlayStyle(blur: true),
-    // );
-  }
 }
 }
 
 
 class BuildEmojiPickerView extends StatefulWidget {
 class BuildEmojiPickerView extends StatefulWidget {
-  const BuildEmojiPickerView({Key? key, required this.controller, this.offset})
-      : super(key: key);
-
-  final QuillController controller;
+  const BuildEmojiPickerView({Key? key, this.offset}) : super(key: key);
   final Offset? offset;
   final Offset? offset;
 
 
   @override
   @override
@@ -172,15 +131,15 @@ class _BuildEmojiPickerViewState extends State<BuildEmojiPickerView> {
   }
   }
 
 
   void insertEmoji(Emoji emoji) {
   void insertEmoji(Emoji emoji) {
-    final baseOffset = widget.controller.selection.baseOffset;
-    final extentOffset = widget.controller.selection.extentOffset;
-    final replaceLen = extentOffset - baseOffset;
-    final selection = widget.controller.selection.copyWith(
-      baseOffset: baseOffset + emoji.emoji.length,
-      extentOffset: baseOffset + emoji.emoji.length,
-    );
+    // final baseOffset = widget.controller.selection.baseOffset;
+    // final extentOffset = widget.controller.selection.extentOffset;
+    // final replaceLen = extentOffset - baseOffset;
+    // final selection = widget.controller.selection.copyWith(
+    //   baseOffset: baseOffset + emoji.emoji.length,
+    //   extentOffset: baseOffset + emoji.emoji.length,
+    // );
 
 
-    widget.controller
-        .replaceText(baseOffset, replaceLen, emoji.emoji, selection);
+    // widget.controller
+    //     .replaceText(baseOffset, replaceLen, emoji.emoji, selection);
   }
   }
 }
 }

+ 0 - 163
frontend/app_flowy/pubspec.lock

@@ -246,13 +246,6 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "1.2.0"
     version: "1.2.0"
-  cross_file:
-    dependency: transitive
-    description:
-      name: cross_file
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "0.3.3+1"
   crypto:
   crypto:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -466,34 +459,6 @@ packages:
     description: flutter
     description: flutter
     source: sdk
     source: sdk
     version: "0.0.0"
     version: "0.0.0"
-  flutter_inappwebview:
-    dependency: transitive
-    description:
-      name: flutter_inappwebview
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "5.4.3+7"
-  flutter_keyboard_visibility:
-    dependency: transitive
-    description:
-      name: flutter_keyboard_visibility
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "5.2.0"
-  flutter_keyboard_visibility_platform_interface:
-    dependency: transitive
-    description:
-      name: flutter_keyboard_visibility_platform_interface
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "2.0.0"
-  flutter_keyboard_visibility_web:
-    dependency: transitive
-    description:
-      name: flutter_keyboard_visibility_web
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "2.0.0"
   flutter_lints:
   flutter_lints:
     dependency: "direct dev"
     dependency: "direct dev"
     description:
     description:
@@ -513,15 +478,6 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "2.0.6"
     version: "2.0.6"
-  flutter_quill:
-    dependency: "direct main"
-    description:
-      path: "."
-      ref: "306fd78b7a134abdde0fed6be67f59e8a6068509"
-      resolved-ref: "306fd78b7a134abdde0fed6be67f59e8a6068509"
-      url: "https://github.com/appflowy/flutter-quill.git"
-    source: git
-    version: "2.0.13"
   flutter_svg:
   flutter_svg:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -579,13 +535,6 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "7.2.0"
     version: "7.2.0"
-  gettext_parser:
-    dependency: transitive
-    description:
-      name: gettext_parser
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "0.2.0"
   glob:
   glob:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -642,48 +591,6 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "4.0.1"
     version: "4.0.1"
-  i18n_extension:
-    dependency: transitive
-    description:
-      name: i18n_extension
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "4.2.1"
-  image_picker:
-    dependency: transitive
-    description:
-      name: image_picker
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "0.8.5+3"
-  image_picker_android:
-    dependency: transitive
-    description:
-      name: image_picker_android
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "0.8.4+13"
-  image_picker_for_web:
-    dependency: transitive
-    description:
-      name: image_picker_for_web
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "2.1.8"
-  image_picker_ios:
-    dependency: transitive
-    description:
-      name: image_picker_ios
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "0.8.5+5"
-  image_picker_platform_interface:
-    dependency: transitive
-    description:
-      name: image_picker_platform_interface
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "2.5.0"
   integration_test:
   integration_test:
     dependency: "direct dev"
     dependency: "direct dev"
     description: flutter
     description: flutter
@@ -955,13 +862,6 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "2.0.6"
     version: "2.0.6"
-  pedantic:
-    dependency: transitive
-    description:
-      name: pedantic
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "1.11.1"
   petitparser:
   petitparser:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -969,13 +869,6 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "5.0.0"
     version: "5.0.0"
-  photo_view:
-    dependency: transitive
-    description:
-      name: photo_view
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "0.13.0"
   platform:
   platform:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -1233,13 +1126,6 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "1.8.2"
     version: "1.8.2"
-  sprintf:
-    dependency: transitive
-    description:
-      name: sprintf
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "6.0.0"
   stack_trace:
   stack_trace:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -1268,13 +1154,6 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "1.1.0"
     version: "1.1.0"
-  string_validator:
-    dependency: transitive
-    description:
-      name: string_validator
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "0.3.0"
   styled_widget:
   styled_widget:
     dependency: "direct main"
     dependency: "direct main"
     description:
     description:
@@ -1443,41 +1322,6 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "2.1.2"
     version: "2.1.2"
-  video_player:
-    dependency: transitive
-    description:
-      name: video_player
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "2.4.2"
-  video_player_android:
-    dependency: transitive
-    description:
-      name: video_player_android
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "2.3.4"
-  video_player_avfoundation:
-    dependency: transitive
-    description:
-      name: video_player_avfoundation
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "2.3.4"
-  video_player_platform_interface:
-    dependency: transitive
-    description:
-      name: video_player_platform_interface
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "5.1.2"
-  video_player_web:
-    dependency: transitive
-    description:
-      name: video_player_web
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "2.0.10"
   vm_service:
   vm_service:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -1550,13 +1394,6 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "3.1.1"
     version: "3.1.1"
-  youtube_player_flutter:
-    dependency: transitive
-    description:
-      name: youtube_player_flutter
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "8.1.0"
 sdks:
 sdks:
   dart: ">=2.17.0 <3.0.0"
   dart: ">=2.17.0 <3.0.0"
   flutter: ">=3.0.0"
   flutter: ">=3.0.0"

+ 0 - 4
frontend/app_flowy/pubspec.yaml

@@ -43,10 +43,6 @@ dependencies:
     path: packages/appflowy_editor
     path: packages/appflowy_editor
   appflowy_popover:
   appflowy_popover:
     path: packages/appflowy_popover
     path: packages/appflowy_popover
-  flutter_quill:
-    git:
-      url: https://github.com/appflowy/flutter-quill.git
-      ref: 306fd78b7a134abdde0fed6be67f59e8a6068509
 
 
   #  third party packages
   #  third party packages
   intl: ^0.17.0
   intl: ^0.17.0